Beispiel #1
0
    def start(cls):
        Tools.ProcessArguments(Modules)
        call(["clear"])
        Tools.PrintHeader()
        Core.PrintMenuAndGetDesiredFeatures()

        if var.kernel or Modules.GetFiles():
            Core.GetDesiredKernel()

        Core.VerifySupportedArchitecture()
        Tools.Clean()
        Core.VerifyPreliminaryBinaries()
        Core.CreateBaselayout()
        Core.VerifyBinaries()
        Core.CopyBinaries()
        Core.CopyManPages()
        Core.CopyModules()
        Core.CopyFirmware()

        # Dependencies must be copied before we create links since the commands inside of
        # the create links (i.e chroot) function require that the libraries are already
        # in our chroot environment.
        Core.CopyDependencies()
        Core.CreateLinks()

        Core.LastSteps()
        Core.CreateInitramfs()
        Tools.CleanAndExit(var.initrd)
Beispiel #2
0
    def CopyModules(cls):
        moddeps = set()

        # Build the list of module dependencies
        Tools.Info("Copying modules ...")

        # Checks to see if all the modules in the list exist (if any)
        for file in Modules.GetFiles():
            try:
                cmd = ("find " + var.modules + ' -iname "' + file +
                       '.ko" | grep ' + file + ".ko")
                result = check_output(cmd, universal_newlines=True,
                                      shell=True).strip()
                cls._modset.add(result)
            except CalledProcessError:
                Tools.ModuleDoesntExist(file)

        # If a kernel has been set, try to update the module dependencies
        # database before searching it
        if var.kernel:
            try:
                result = call(["depmod", var.kernel])

                if result:
                    Tools.Fail("Error updating module dependency database!")
            except FileNotFoundError:
                # This should never occur because the application checks
                # that root is the user that is running the application.
                # Non-administraative users normally don't have access
                # to the 'depmod' command.
                Tools.Fail("The 'depmod' command wasn't found.")

        # Get the dependencies for all the modules in our set
        for file in cls._modset:
            # Get only the name of the module
            match = re.search("(?<=/)[a-zA-Z0-9_-]+.ko", file)

            if match:
                sFile = match.group().split(".")[0]

                cmd = ("modprobe -S " + var.kernel + " --show-depends " +
                       sFile + " | awk -F ' ' '{print $2}'")
                results = check_output(cmd,
                                       shell=True,
                                       universal_newlines=True).strip()

                for i in results.split("\n"):
                    moddeps.add(i.strip())

        # Copy the modules/dependencies
        if moddeps:
            for module in moddeps:
                Tools.Copy(module)

            # Update module dependency database inside the initramfs
            cls.GenerateModprobeInfo()
Beispiel #3
0
    def AddFilesAfterSettingsLoaded(cls):
        """Adds required files to different hooks after the settings are loaded."""

        # The udev provider is also part of the base required files. However,
        # we are simplifying it to only one entry in the json so that if the
        # user's provider defers, they only need to change it in one place.
        Base.AddFile(Base.GetUdevProvider())

        # Add the required ZFS module
        Modules.AddFile("zfs")
Beispiel #4
0
    def LastSteps(cls):
        Tools.Info("Performing finishing steps ...")

        # Create mtab file
        call(["touch", var.temp + "/etc/mtab"])

        if not os.path.isfile(var.temp + "/etc/mtab"):
            Tools.Fail("Error creating the mtab file. Exiting.")

        cls.CreateLibraryLinks()

        # Copy the init script
        Tools.SafeCopy(var.files_dir + "/init", var.temp)

        # Give execute permissions to the script
        cr = call(["chmod", "u+x", var.temp + "/init"])

        if cr != 0:
            Tools.Fail("Failed to give executive privileges to " + var.temp +
                       "/init")

        # Sets initramfs script version number
        cmd = f"echo {var.version} > {var.temp}/version.bliss"
        call(cmd, shell=True)

        # Copy all of the modprobe configurations
        if os.path.isdir("/etc/modprobe.d/"):
            shutil.copytree("/etc/modprobe.d/", var.temp + "/etc/modprobe.d/")

        cls.CopyUdevAndSupportFiles()
        cls.DumpSystemKeymap()

        # Any last substitutions or additions/modifications should be done here

        if Luks.IsEnabled():
            # Copy over our keyfile if the user activated it
            if Luks.IsKeyfileEnabled():
                Tools.Flag("Embedding our keyfile into the initramfs...")
                Tools.SafeCopy(Luks.GetKeyfilePath(), var.temp + "/etc",
                               "keyfile")

            # Copy over our detached header if the user activated it
            if Luks.IsDetachedHeaderEnabled():
                Tools.Flag(
                    "Embedding our detached header into the initramfs...")
                Tools.SafeCopy(Luks.GetDetachedHeaderPath(), var.temp + "/etc",
                               "header")

        # Add any modules needed into the initramfs
        requiredModules = ",".join(Modules.GetFiles())
        cmd = f"echo {requiredModules} > {var.temp}/modules.bliss"
        call(cmd, shell=True)

        cls.CopyLibGccLibrary()
Beispiel #5
0
    def LastSteps(cls):
        """Performes any last minute steps like copying zfs.conf,
           giving init execute permissions, setting up symlinks, etc.
        """
        Tools.Info("Performing finishing steps ...")

        # Create mtab file
        call(["touch", var.temp + "/etc/mtab"])

        if not os.path.isfile(var.temp + "/etc/mtab"):
            Tools.Fail("Error creating the mtab file. Exiting.")

        cls.CreateLibraryLinks()

        # Copy the init script
        Tools.SafeCopy(var.filesDirectory + "/init", var.temp)

        # Give execute permissions to the script
        cr = call(["chmod", "u+x", var.temp + "/init"])

        if cr != 0:
            Tools.Fail("Failed to give executive privileges to " + var.temp + "/init")

        # Sets initramfs script version number
        cmd = f"echo {var.version} > {var.temp}/version.bliss"
        call(cmd, shell=True)

        # Copy all of the modprobe configurations
        if os.path.isdir(var.modprobeDirectory):
            Tools.CopyTree(var.modprobeDirectory, var.temp + var.modprobeDirectory)

        cls.CopyUdevAndSupportFiles()
        cls.DumpSystemKeymap()

        # Any last substitutions or additions/modifications should be done here

        # Add any modules needed into the initramfs
        requiredModules = ",".join(Modules.GetFiles())
        cmd = f"echo {requiredModules} > {var.temp}/modules.bliss"
        call(cmd, shell=True)

        cls.CopyLibGccLibrary()
Beispiel #6
0
    def PrintMenuAndGetDesiredFeatures(cls):
        # If the user didn't pass their desired features through the command
        # line, then ask them which initramfs they would like to generate.
        if not var.features:
            print(
                "Which initramfs features do you want? (Separated by a comma):"
            )
            Tools.PrintFeatures()
            var.features = Tools.Question("Features [1]: ")

            if var.features:
                var.features = cls.ConvertNumberedFeaturesToNamedList(
                    var.features)
            else:
                var.features = ["zfs"]

            Tools.NewLine()
        else:
            var.features = var.features.split(",")

        for feature in var.features:
            if feature == "zfs":
                Zfs.Enable()
                Modules.AddFile("zfs")
            elif feature == "luks":
                Luks.Enable()
            # Just a base initramfs with no additional stuff
            # This can be used with other options though
            # (i.e you have your rootfs directly on top of LUKS)
            elif feature == "basic":
                pass
            elif feature == "exit":
                Tools.Warn("Exiting.")
                quit(0)
            else:
                Tools.Warn("Invalid Option. Exiting.")
                quit(1)
Beispiel #7
0
class Core:
    # List of binaries (That will be 'ldd'ed later)
    _binset = set()

    # List of modules that will be compressed
    _modset = set()

    # Enable the 'base' hook since all initramfs will have this
    Base.Enable()

    # Modules will now always be enabled since all initramfs can have
    # the ability to have 0 or more modules.
    Modules.Enable()

    @classmethod
    # Prints the menu and accepts user features
    def PrintMenuAndGetDesiredFeatures(cls):
        # If the user didn't pass their desired features through the command
        # line, then ask them which initramfs they would like to generate.
        if not var.features:
            print(
                "Which initramfs features do you want? (Separated by a comma):"
            )
            Tools.PrintFeatures()
            var.features = Tools.Question("Features [1]: ")

            if var.features:
                var.features = cls.ConvertNumberedFeaturesToNamedList(
                    var.features)
            else:
                var.features = ["zfs"]

            Tools.NewLine()
        else:
            var.features = var.features.split(",")

        for feature in var.features:
            if feature == "zfs":
                Zfs.Enable()
                Modules.AddFile("zfs")
            elif feature == "luks":
                Luks.Enable()
            # Just a base initramfs with no additional stuff
            # This can be used with other options though
            # (i.e you have your rootfs directly on top of LUKS)
            elif feature == "basic":
                pass
            elif feature == "exit":
                Tools.Warn("Exiting.")
                quit(0)
            else:
                Tools.Warn("Invalid Option. Exiting.")
                quit(1)

    # Returns the name equivalent list of a numbered list of features
    @classmethod
    def ConvertNumberedFeaturesToNamedList(cls, numbered_feature_list):
        named_features = []

        try:
            for feature in numbered_feature_list.split(","):
                feature_as_string = Tools._features[int(feature)].lower()
                named_features.append(feature_as_string)
        except KeyError:
            named_features.clear()
            named_features.append("exit")

        return named_features

    # Creates the base directory structure
    @classmethod
    def CreateBaselayout(cls):
        Tools.Info("Creating temporary directory at " + var.temp + " ...")

        for dir in var.baselayout:
            call(["mkdir", "-p", dir])

    # Ask the user if they want to use their current kernel, or another one
    @classmethod
    def GetDesiredKernel(cls):
        if not var.kernel:
            current_kernel = check_output(["uname", "-r"],
                                          universal_newlines=True).strip()

            message = ("Do you want to use the current kernel: " +
                       current_kernel + " [Y/n]: ")
            choice = Tools.Question(message)
            Tools.NewLine()

            if choice == "y" or choice == "Y" or not choice:
                var.kernel = current_kernel
            elif choice == "n" or choice == "N":
                var.kernel = Tools.Question("Please enter the kernel name: ")
                Tools.NewLine()

                if not var.kernel:
                    Tools.Fail("You didn't enter a kernel. Exiting...")
            else:
                Tools.Fail("Invalid Option. Exiting.")

        # Set modules path to correct location and sets kernel name for initramfs
        var.modules = "/lib/modules/" + var.kernel + "/"
        var.lmodules = var.temp + "/" + var.modules
        var.initrd = "initrd-" + var.kernel

        # Check modules directory
        cls.VerifyModulesDirectory()

    # Check to make sure the kernel modules directory exists
    @classmethod
    def VerifyModulesDirectory(cls):
        if not os.path.exists(var.modules):
            Tools.Fail("The modules directory for " + var.modules +
                       " doesn't exist!")

    # Make sure that the arch is x86_64
    @classmethod
    def VerifySupportedArchitecture(cls):
        if var.arch != "x86_64":
            Tools.Fail("Your architecture isn't supported. Exiting.")

    # Checks to see if the preliminary binaries exist
    @classmethod
    def VerifyPreliminaryBinaries(cls):
        Tools.Info("Checking preliminary binaries ...")

        # If the required binaries don't exist, then exit
        for binary in var.prel_bin:
            if not os.path.isfile(Tools.GetProgramPath(binary)):
                Tools.BinaryDoesntExist(binary)

    # Generates the modprobe information
    @classmethod
    def GenerateModprobeInfo(cls):
        Tools.Info("Generating modprobe information ...")

        # Copy modules.order and modules.builtin just so depmod doesn't spit out warnings. -_-
        Tools.Copy(var.modules + "/modules.order")
        Tools.Copy(var.modules + "/modules.builtin")

        result = call(["depmod", "-b", var.temp, var.kernel])

        if result != 0:
            Tools.Fail(
                "Depmod was unable to refresh the dependency information for your initramfs!"
            )

    # Copies the firmware files if necessary
    @classmethod
    def CopyFirmware(cls):
        if Firmware.IsEnabled():
            Tools.Info("Copying firmware...")

            if os.path.isdir("/lib/firmware/"):
                if Firmware.IsCopyAllEnabled():
                    shutil.copytree("/lib/firmware/",
                                    var.temp + "/lib/firmware/")
                else:
                    # Copy the firmware in the files list
                    if Firmware.GetFiles():
                        try:
                            for fw in Firmware.GetFiles():
                                Tools.Copy(
                                    fw, directoryPrefix=var.firmwareDirectory)
                        except FileNotFoundError:
                            Tools.Warn(
                                "An error occured while copying the following firmware: "
                                + fw)
                    else:
                        Tools.Warn(
                            "No firmware files were found in the firmware list!"
                        )
            else:
                Tools.Fail("The /lib/firmware/ directory does not exist")

    # Create the required symlinks
    @classmethod
    def CreateLinks(cls):
        Tools.Info("Creating symlinks ...")

        # Needs to be from this directory so that the links are relative
        os.chdir(var.lbin)

        # Create busybox links
        cmd = ("chroot " + var.temp +
               ' /bin/busybox sh -c "cd /bin && /bin/busybox --install -s ."')
        callResult = call(cmd, shell=True)

        if callResult != 0:
            Tools.Fail("Unable to create busybox links via chroot!")

        # Create 'sh' symlink to 'bash'
        os.remove(var.temp + "/bin/sh")
        os.symlink("bash", "sh")

        # Switch to the kmod directory, delete the corresponding busybox
        # symlink and create the symlinks pointing to kmod
        if os.path.isfile(var.lsbin + "/kmod"):
            os.chdir(var.lsbin)
        elif os.path.isfile(var.lbin + "/kmod"):
            os.chdir(var.lbin)

        for link in Base.GetKmodLinks():
            os.remove(var.temp + "/bin/" + link)
            os.symlink("kmod", link)

    # Creates symlinks from library files found in each /usr/lib## dir to the /lib[32/64] directories
    @classmethod
    def CreateLibraryLinks(cls):
        if os.path.isdir(var.temp + "/usr/lib") and os.path.isdir(var.temp +
                                                                  "/lib64"):
            cls.FindAndCreateLinks("/usr/lib/", "/lib64")

        if os.path.isdir(var.temp + "/usr/lib32") and os.path.isdir(var.temp +
                                                                    "/lib32"):
            cls.FindAndCreateLinks("/usr/lib32/", "/lib32")

        if os.path.isdir(var.temp + "/usr/lib64") and os.path.isdir(var.temp +
                                                                    "/lib64"):
            cls.FindAndCreateLinks("/usr/lib64/", "/lib64")

        # Create links to libraries found within /lib itself
        if os.path.isdir(var.temp + "/lib") and os.path.isdir(var.temp +
                                                              "/lib"):
            cls.FindAndCreateLinks("/lib/", "/lib")

    @classmethod
    def FindAndCreateLinks(cls, sourceDirectory, targetDirectory):
        pcmd = ("find " + sourceDirectory +
                ' -iname "*.so.*" -exec ln -sf "{}" ' + targetDirectory +
                " \;")
        cmd = f'chroot {var.temp} /bin/busybox sh -c "{pcmd}"'
        call(cmd, shell=True)

        pcmd = ("find " + sourceDirectory +
                ' -iname "*.so" -exec ln -sf "{}" ' + targetDirectory + " \;")
        cmd = f'chroot {var.temp} /bin/busybox sh -c "{pcmd}"'
        call(cmd, shell=True)

    # Copies udev and files that udev uses, like /etc/udev/*, /lib/udev/*, etc
    @classmethod
    def CopyUdevAndSupportFiles(cls):
        # Copy all of the udev files
        udev_conf_dir = "/etc/udev/"
        temp_udev_conf_dir = var.temp + udev_conf_dir

        if os.path.isdir(udev_conf_dir):
            shutil.copytree(udev_conf_dir, temp_udev_conf_dir)

        udev_lib_dir = "/lib/udev/"
        temp_udev_lib_dir = var.temp + udev_lib_dir

        if os.path.isdir(udev_lib_dir):
            shutil.copytree(udev_lib_dir, temp_udev_lib_dir)

        # Rename udevd and place in /sbin
        udev_path = Tools.GetUdevPath()
        systemd_dir = os.path.dirname(udev_path)

        sbin_udevd = var.sbin + "/udevd"
        udev_path_temp = var.temp + udev_path

        if os.path.isfile(udev_path_temp) and udev_path != sbin_udevd:
            udev_path_new = var.temp + sbin_udevd
            os.rename(udev_path_temp, udev_path_new)

            temp_systemd_dir = var.temp + systemd_dir

            # If the directory is empty, than remove it.
            # With the recent gentoo systemd root prefix move, it is moving to
            # /lib/systemd. Thus this directory also contains systemd dependencies
            # such as: libsystemd-shared-###.so
            # https://gentoo.org/support/news-items/2017-07-16-systemd-rootprefix.html
            if not os.listdir(temp_systemd_dir):
                os.rmdir(temp_systemd_dir)

    # Dumps the current system's keymap
    @classmethod
    def DumpSystemKeymap(cls):
        pathToKeymap = var.temp + "/etc/keymap"
        result = call("dumpkeys > " + pathToKeymap, shell=True)

        if result != 0 or not os.path.isfile(pathToKeymap):
            Tools.Warn(
                "There was an error dumping the system's current keymap. Ignoring."
            )

    # This functions does any last minute steps like copying zfs.conf,
    # giving init execute permissions, setting up symlinks, etc
    @classmethod
    def LastSteps(cls):
        Tools.Info("Performing finishing steps ...")

        # Create mtab file
        call(["touch", var.temp + "/etc/mtab"])

        if not os.path.isfile(var.temp + "/etc/mtab"):
            Tools.Fail("Error creating the mtab file. Exiting.")

        cls.CreateLibraryLinks()

        # Copy the init script
        Tools.SafeCopy(var.files_dir + "/init", var.temp)

        # Give execute permissions to the script
        cr = call(["chmod", "u+x", var.temp + "/init"])

        if cr != 0:
            Tools.Fail("Failed to give executive privileges to " + var.temp +
                       "/init")

        # Sets initramfs script version number
        cmd = f"echo {var.version} > {var.temp}/version.bliss"
        call(cmd, shell=True)

        # Copy all of the modprobe configurations
        if os.path.isdir("/etc/modprobe.d/"):
            shutil.copytree("/etc/modprobe.d/", var.temp + "/etc/modprobe.d/")

        cls.CopyUdevAndSupportFiles()
        cls.DumpSystemKeymap()

        # Any last substitutions or additions/modifications should be done here

        if Luks.IsEnabled():
            # Copy over our keyfile if the user activated it
            if Luks.IsKeyfileEnabled():
                Tools.Flag("Embedding our keyfile into the initramfs...")
                Tools.SafeCopy(Luks.GetKeyfilePath(), var.temp + "/etc",
                               "keyfile")

            # Copy over our detached header if the user activated it
            if Luks.IsDetachedHeaderEnabled():
                Tools.Flag(
                    "Embedding our detached header into the initramfs...")
                Tools.SafeCopy(Luks.GetDetachedHeaderPath(), var.temp + "/etc",
                               "header")

        # Add any modules needed into the initramfs
        requiredModules = ",".join(Modules.GetFiles())
        cmd = f"echo {requiredModules} > {var.temp}/modules.bliss"
        call(cmd, shell=True)

        cls.CopyLibGccLibrary()

    # Copy the 'libgcc' library so that when libpthreads loads it during runtime.
    # https://github.com/zfsonlinux/zfs/issues/4749
    @classmethod
    def CopyLibGccLibrary(cls):
        # Find the correct path for libgcc
        libgcc_filename = "libgcc_s.so"
        libgcc_filename_main = libgcc_filename + ".1"

        # check for gcc-config
        cmd = 'whereis gcc-config | cut -d " " -f 2'
        res = Tools.Run(cmd)

        if res:
            # Try gcc-config
            cmd = "gcc-config -L | cut -d ':' -f 1"
            res = Tools.Run(cmd)

            if res:
                # Use path from gcc-config
                libgcc_path = res[0] + "/" + libgcc_filename_main
                Tools.SafeCopy(libgcc_path, var.llib64)
                os.chdir(var.llib64)
                os.symlink(libgcc_filename_main, libgcc_filename)
                return

        # Doing a 'whereis <name of libgcc library>' will not work because it seems
        # that it finds libraries in /lib, /lib64, /usr/lib, /usr/lib64, but not in
        # /usr/lib/gcc/ (x86_64-pc-linux-gnu/5.4.0, etc)

        # When a better approach is found, we can plug it in here directly and return
        # in the event that it succeeds. If it fails, we just continue execution
        # until the end of the function.

        # If we've reached this point, we have failed to copy the gcc library.
        Tools.Fail("Unable to retrieve the gcc library path!")

    # Create the initramfs
    @classmethod
    def CreateInitramfs(cls):
        Tools.Info("Creating the initramfs ...")

        # The find command must use the `find .` and not `find ${T}`
        # because if not, then the initramfs layout will be prefixed with
        # the ${T} path.
        os.chdir(var.temp)

        call(
            [
                "find . -print0 | cpio -o --null --format=newc | gzip -9 > " +
                var.home + "/" + var.initrd
            ],
            shell=True,
        )

        if not os.path.isfile(var.home + "/" + var.initrd):
            Tools.Fail("Error creating the initramfs. Exiting.")

    # Checks to see if the binaries exist, if not then emerge
    @classmethod
    def VerifyBinaries(cls):
        Tools.Info("Checking required files ...")

        # Check required base files
        cls.VerifyBinariesExist(Base.GetFiles())

        # Check required luks files
        if Luks.IsEnabled():
            Tools.Flag("Using LUKS")
            cls.VerifyBinariesExist(Luks.GetFiles())

        # Check required zfs files
        if Zfs.IsEnabled():
            Tools.Flag("Using ZFS")
            cls.VerifyBinariesExist(Zfs.GetFiles())

    # Checks to see that all the binaries in the array exist and errors if they don't
    @classmethod
    def VerifyBinariesExist(cls, vFiles):
        for file in vFiles:
            if not os.path.exists(file):
                Tools.BinaryDoesntExist(file)

    # Copies the required files into the initramfs
    @classmethod
    def CopyBinaries(cls):
        Tools.Info("Copying binaries ...")

        cls.FilterAndInstall(Base.GetFiles())

        if Luks.IsEnabled():
            cls.FilterAndInstall(Luks.GetFiles())

        if Zfs.IsEnabled():
            cls.FilterAndInstall(Zfs.GetFiles())
            cls.FilterAndInstall(Zfs.GetOptionalFiles(), dontFail=True)

    # Copies the man pages (driver)
    @classmethod
    def CopyManPages(cls):
        if Zfs.IsEnabled() and Zfs.IsManEnabled():
            Tools.Info("Copying man pages ...")
            cls.CopyMan(Zfs.GetManPages())

    # Depending the ZFS version that the user is running,
    # some manual pages that the initramfs wants to copy might not
    # have yet been written. Therefore, attempt to copy the man pages,
    # but if we are unable to copy, then just continue.
    @classmethod
    def CopyMan(cls, files):
        for f in files:
            Tools.Copy(f, dontFail=True)

    # Filters and installs each file in the array into the initramfs
    # Optional Args:
    #   dontFail - Same description as the one in Tools.Copy
    @classmethod
    def FilterAndInstall(cls, vFiles, **optionalArgs):
        for file in vFiles:
            # If the application is a binary, add it to our binary set. If the application is not
            # a binary, then we will get a CalledProcessError because the output will be null.
            try:
                check_output(
                    "file -L " + file.strip() + ' | grep "linked"',
                    shell=True,
                    universal_newlines=True,
                ).strip()
                cls._binset.add(file)
            except CalledProcessError:
                pass

            # Copy the file into the initramfs
            Tools.Copy(file, dontFail=optionalArgs.get("dontFail", False))

    # Copy modules and their dependencies
    @classmethod
    def CopyModules(cls):
        moddeps = set()

        # Build the list of module dependencies
        Tools.Info("Copying modules ...")

        # Checks to see if all the modules in the list exist (if any)
        for file in Modules.GetFiles():
            try:
                cmd = ("find " + var.modules + ' -iname "' + file +
                       '.ko" | grep ' + file + ".ko")
                result = check_output(cmd, universal_newlines=True,
                                      shell=True).strip()
                cls._modset.add(result)
            except CalledProcessError:
                Tools.ModuleDoesntExist(file)

        # If a kernel has been set, try to update the module dependencies
        # database before searching it
        if var.kernel:
            try:
                result = call(["depmod", var.kernel])

                if result:
                    Tools.Fail("Error updating module dependency database!")
            except FileNotFoundError:
                # This should never occur because the application checks
                # that root is the user that is running the application.
                # Non-administraative users normally don't have access
                # to the 'depmod' command.
                Tools.Fail("The 'depmod' command wasn't found.")

        # Get the dependencies for all the modules in our set
        for file in cls._modset:
            # Get only the name of the module
            match = re.search("(?<=/)[a-zA-Z0-9_-]+.ko", file)

            if match:
                sFile = match.group().split(".")[0]

                cmd = ("modprobe -S " + var.kernel + " --show-depends " +
                       sFile + " | awk -F ' ' '{print $2}'")
                results = check_output(cmd,
                                       shell=True,
                                       universal_newlines=True).strip()

                for i in results.split("\n"):
                    moddeps.add(i.strip())

        # Copy the modules/dependencies
        if moddeps:
            for module in moddeps:
                Tools.Copy(module)

            # Update module dependency database inside the initramfs
            cls.GenerateModprobeInfo()

    # Gets the library dependencies for all our binaries and copies them into our initramfs.
    @classmethod
    def CopyDependencies(cls):
        Tools.Info("Copying library dependencies ...")

        bindeps = set()

        # Musl and non-musl systems are supported.
        possible_libc_paths = [
            var.lib64 + "/ld-linux-x86-64.so*",
            var.lib + "/ld-musl-x86_64.so*",
        ]
        libc_found = False

        for libc in possible_libc_paths:
            try:
                # (Dirty implementation) Use the exit code of grep with no messages being outputed to see if this interpreter exists.
                # We don't know the name yet which is why we are using the wildcard in the variable declaration.
                result = call("grep -Uqs thiswillnevermatch " + libc,
                              shell=True)

                # 0 = match found
                # 1 = file exists but not found
                # 2 = file doesn't exist
                # In situations 0 or 1, we are good, since we just care that the file exists.
                if result != 0 and result != 1:
                    continue

                # Get the interpreter name that is on this system
                result = check_output("ls " + libc,
                                      shell=True,
                                      universal_newlines=True).strip()

                # Add intepreter to deps since everything will depend on it
                bindeps.add(result)
                libc_found = True
            except Exception as e:
                pass

        if not libc_found:
            Tools.Fail("No libc interpreters were found!")

        # Get the dependencies for the binaries we've collected and add them to
        # our bindeps set. These will all be copied into the initramfs later.
        for binary in cls._binset:
            cmd = (
                "ldd " + binary +
                " | awk -F '=>' '{print $2}' | awk -F ' ' '{print $1}' | sed '/^ *$/d'"
            )
            results = check_output(cmd, shell=True,
                                   universal_newlines=True).strip()

            if results:
                for library in results.split("\n"):
                    bindeps.add(library)

        # Copy all the dependencies of the binary files into the initramfs
        for library in bindeps:
            Tools.Copy(library)
Beispiel #8
0
class Core:
    """Contains the core of the application"""

    # List of binaries (That will be 'ldd'ed later)
    _binset = set()

    # List of modules that will be compressed
    _modset = set()

    # Enable the 'base' hook since all initramfs will have this
    Base.Enable()

    # Enable the 'zfs' hook since all initramfs will have this
    Zfs.Enable()

    # Enable the 'modules' hook since all initramfs will have this
    Modules.Enable()

    @classmethod
    def LoadSettings(cls):
        """Loads all of the settings from the json file into the Hooks."""

        # This approach was taken since it was the easiest / cleanest
        # implementation in terms of keeping the application references
        # mostly the same, but still being able to load the external info.
        settings = Tools.LoadSettings()

        # Base
        Base._files = settings["base"]["files"]
        Base._kmod_links = settings["base"]["kmodLinks"]
        Base._udev_provider = settings["base"]["udevProvider"]

        # Modules

        # A list of kernel modules to include in the initramfs
        # Format: "module1", "module2", "module3", ...

        # Example: To enable nvme and i915 you would have the following
        # modules in your settings.json: [nvme", "i915"]
        Modules._files = settings["modules"]["files"]

        # ZFS

        # Required Files
        Zfs._files = settings["zfs"]["files"]

        # Optional Files. Will not fail if we fail to copy them.
        Zfs._optional_files = settings["zfs"]["optionalFiles"]

        # Man Pages. Not used for actual initramfs environment
        # since the initramfs doesn't have the applications required to
        # display these man pages without increasing the size a lot. However,
        # these are used by the 'sysresccd-moddat' scripts to generate
        # the sysresccd + zfs isos.
        # Should we copy the man pages?
        Zfs._use_man = settings["zfs"]["useMan"]

        # Note: Portage allows one to change the compression type with
        # PORTAGE_COMPRESS. In this situation, these files will have
        # a different extension. The user should adjust these if needed.
        Zfs._man = settings["zfs"]["manFiles"]

        # Firmware

        # Copy firmware?
        Firmware._use = settings["firmware"]["use"]

        # If enabled, all the firmware in /lib/firmware will be copied into the initramfs.
        # If you know exactly what firmware files you want, definitely leave this at 0 so
        # to reduce the initramfs size.
        Firmware._copy_all = settings["firmware"]["copyAll"]

        # A list of firmware files to include in the initramfs
        Firmware._files = settings["firmware"]["files"]

        # A list of firmware directories to include in the initramfs
        Firmware._directories = settings["firmware"]["directories"]

        # Variables
        var.bin = settings["systemDirectory"]["bin"]
        var.sbin = settings["systemDirectory"]["sbin"]
        var.lib = settings["systemDirectory"]["lib"]
        var.lib64 = settings["systemDirectory"]["lib64"]
        var.etc = settings["systemDirectory"]["etc"]

        # Preliminary binaries needed for the success of creating the initrd
        # but that are not needed to be placed inside the initrd
        var.preliminaryBinaries = settings["preliminaryBuildBinaries"]

        var.modulesDirectory = settings["modulesDirectory"]
        var.firmwareDirectory = settings["firmwareDirectory"]
        var.initrdPrefix = settings["initrdPrefix"]
        var.modprobeDirectory = settings["modprobeDirectory"]
        var.udevEtcDirectory = settings["udev"]["etc"]["baseDirectory"]
        var.udevEtcExcludedFiles = settings["udev"]["etc"]["excludedFiles"]
        var.udevLibDirectory = settings["udev"]["lib"]["baseDirectory"]
        var.udevLibExcludedFiles = settings["udev"]["lib"]["excludedFiles"]

    @classmethod
    def AddFilesAfterSettingsLoaded(cls):
        """Adds required files to different hooks after the settings are loaded."""

        # The udev provider is also part of the base required files. However,
        # we are simplifying it to only one entry in the json so that if the
        # user's provider defers, they only need to change it in one place.
        Base.AddFile(Base.GetUdevProvider())

        # Add the required ZFS module
        Modules.AddFile("zfs")

    @classmethod
    def CreateBaselayout(cls):
        """Creates the base directory structure."""
        Tools.Info("Creating temporary directory at " + var.temp + " ...")

        for dir in var.baselayout:
            call(["mkdir", "-p", dir])

    @classmethod
    def SetAndCheckDesiredKernel(cls):
        """Sets kernel related variables and modules check."""

        # Set modules path to correct location and sets kernel name for initramfs
        var.modules = var.modulesDirectory + "/" + var.kernel + "/"
        var.lmodules = var.temp + "/" + var.modules
        var.initrd = var.initrdPrefix + var.kernel

        # Check modules directory
        cls.VerifyModulesDirectory()

    @classmethod
    def VerifyModulesDirectory(cls):
        """Check to make sure the kernel modules directory exists."""
        if not os.path.exists(var.modules):
            Tools.Fail("The modules directory for " + var.modules + " doesn't exist!")

    @classmethod
    def VerifySupportedArchitecture(cls):
        """Checks to see that the architecture is supported."""
        if var.arch != "x86_64":
            Tools.Fail("Your architecture isn't supported. Exiting.")

    @classmethod
    def VerifyPreliminaryBinaries(cls):
        """Checks to see if the preliminary binaries exist."""
        Tools.Info("Checking preliminary binaries ...")

        # If the required binaries don't exist, then exit
        for binary in var.preliminaryBinaries:
            if not os.path.isfile(Tools.GetProgramPath(binary)):
                Tools.BinaryDoesntExist(binary)

    @classmethod
    def GenerateModprobeInfo(cls):
        """Generates the modprobe information."""
        Tools.Info("Generating modprobe information ...")

        # Copy modules.order and modules.builtin just so depmod doesn't spit out warnings. -_-
        Tools.Into(var.modules + "/modules.order")
        Tools.Into(var.modules + "/modules.builtin")

        result = call(["depmod", "-b", var.temp, var.kernel])

        if result != 0:
            Tools.Fail(
                "Depmod was unable to refresh the dependency information for your initramfs!"
            )

    @classmethod
    def CopyFirmware(cls):
        """Copies the firmware files/directories if necessary."""
        if Firmware.IsEnabled():
            Tools.Info("Copying firmware...")

            if os.path.isdir(var.firmwareDirectory):
                if Firmware.IsCopyAllEnabled():
                    Tools.CopyTree(
                        var.firmwareDirectory, var.temp + var.firmwareDirectory
                    )
                else:
                    # Copy the firmware files
                    if Firmware.GetFiles():
                        try:
                            for fw in Firmware.GetFiles():
                                Tools.Into(fw, directoryPrefix=var.firmwareDirectory)
                        except FileNotFoundError:
                            Tools.Warn(
                                "An error occurred while copying the following firmware file: {}".format(
                                    fw
                                )
                            )

                    # Copy the firmware directories
                    if Firmware.GetDirectories():
                        try:
                            for fw in Firmware.GetDirectories():
                                sourceFirmwareDirectory = os.path.join(
                                    var.firmwareDirectory, fw
                                )
                                targetFirmwareDirectory = (
                                    var.temp + sourceFirmwareDirectory
                                )
                                Tools.CopyTree(
                                    sourceFirmwareDirectory, targetFirmwareDirectory
                                )
                        except FileNotFoundError:
                            Tools.Warn(
                                "An error occurred while copying the following directory: {}".format(
                                    fw
                                )
                            )

            else:
                Tools.Fail(
                    "The {} directory does not exist".format(var.firmwareDirectory)
                )

    @classmethod
    def CreateLinks(cls):
        """Create the required symlinks."""
        Tools.Info("Creating symlinks ...")

        # Needs to be from this directory so that the links are relative
        os.chdir(var.GetTempBinDir())

        # Create busybox links
        cmd = (
            "chroot "
            + var.temp
            + ' /bin/busybox sh -c "cd /bin && /bin/busybox --install -s ."'
        )
        callResult = call(cmd, shell=True)

        if callResult != 0:
            Tools.Fail("Unable to create busybox links via chroot!")

        # Create 'sh' symlink to 'bash'
        os.remove(var.temp + "/bin/sh")
        os.symlink("bash", "sh")

        # Switch to the kmod directory, delete the corresponding busybox
        # symlink and create the symlinks pointing to kmod
        if os.path.isfile(var.GetTempSbinDir() + "/kmod"):
            os.chdir(var.GetTempSbinDir())
        elif os.path.isfile(var.GetTempBinDir() + "/kmod"):
            os.chdir(var.GetTempBinDir())

        for link in Base.GetKmodLinks():
            os.remove(var.temp + "/bin/" + link)
            os.symlink("kmod", link)

    @classmethod
    def CreateLibraryLinks(cls):
        """Creates symlinks from library files found in each /usr/lib## dir to the /lib[32/64] directories."""
        if os.path.isdir(var.temp + "/usr/lib") and os.path.isdir(var.temp + "/lib64"):
            cls._FindAndCreateLinks("/usr/lib/", "/lib64")

        if os.path.isdir(var.temp + "/usr/lib32") and os.path.isdir(
            var.temp + "/lib32"
        ):
            cls._FindAndCreateLinks("/usr/lib32/", "/lib32")

        if os.path.isdir(var.temp + "/usr/lib64") and os.path.isdir(
            var.temp + "/lib64"
        ):
            cls._FindAndCreateLinks("/usr/lib64/", "/lib64")

        # Create links to libraries found within /lib itself
        if os.path.isdir(var.temp + "/lib") and os.path.isdir(var.temp + "/lib"):
            cls._FindAndCreateLinks("/lib/", "/lib")

    @classmethod
    def _FindAndCreateLinks(cls, sourceDirectory, targetDirectory):
        pcmd = (
            "find "
            + sourceDirectory
            + ' -iname "*.so.*" -exec ln -sf "{}" '
            + targetDirectory
            + " \;"
        )
        cmd = f'chroot {var.temp} /bin/busybox sh -c "{pcmd}"'
        call(cmd, shell=True)

        pcmd = (
            "find "
            + sourceDirectory
            + ' -iname "*.so" -exec ln -sf "{}" '
            + targetDirectory
            + " \;"
        )
        cmd = f'chroot {var.temp} /bin/busybox sh -c "{pcmd}"'
        call(cmd, shell=True)

    @classmethod
    def _CopyUdevAndDeleteFiles(cls, udevDirectory, udevExcludedFiles):
        """Helper function to copy udev directory and delete excluded files."""
        tempUdevDirectory = var.temp + udevDirectory

        if os.path.isdir(udevDirectory):
            Tools.CopyTree(udevDirectory, tempUdevDirectory)

        if udevExcludedFiles:
            for udevFile in udevExcludedFiles:
                fileToRemove = tempUdevDirectory + "/" + udevFile

                if os.path.exists(fileToRemove):
                    os.remove(fileToRemove)

    @classmethod
    def CopyUdevAndSupportFiles(cls):
        """Copies udev and files that udev uses, like /etc/udev/*, /lib/udev/*, etc."""
        cls._CopyUdevAndDeleteFiles(var.udevEtcDirectory, var.udevEtcExcludedFiles)
        cls._CopyUdevAndDeleteFiles(var.udevLibDirectory, var.udevLibExcludedFiles)

        # Rename udevd and place in /sbin
        udevProvider = Base.GetUdevProvider()
        providerDir = os.path.dirname(udevProvider)
        tempUdevProvider = var.temp + udevProvider
        sbinUdevd = var.sbin + "/udevd"

        if os.path.isfile(tempUdevProvider) and udevProvider != sbinUdevd:
            tempUdevProviderNew = var.temp + sbinUdevd
            os.rename(tempUdevProvider, tempUdevProviderNew)

            tempProviderDir = var.temp + providerDir

            # If the directory is empty, than remove it.
            # With the recent gentoo systemd root prefix move, it is moving to
            # /lib/systemd. Thus this directory also contains systemd dependencies
            # such as: libsystemd-shared-###.so
            # https://gentoo.org/support/news-items/2017-07-16-systemd-rootprefix.html
            if not os.listdir(tempProviderDir):
                os.rmdir(tempProviderDir)

    @classmethod
    def DumpSystemKeymap(cls):
        """Dumps the current system's keymap."""
        pathToKeymap = var.temp + "/etc/keymap"
        result = call("dumpkeys > " + pathToKeymap, shell=True)

        if result != 0 or not os.path.isfile(pathToKeymap):
            Tools.Warn(
                "There was an error dumping the system's current keymap. Ignoring."
            )

    @classmethod
    def LastSteps(cls):
        """Performes any last minute steps like copying zfs.conf,
           giving init execute permissions, setting up symlinks, etc.
        """
        Tools.Info("Performing finishing steps ...")

        # Create mtab file
        call(["touch", var.temp + "/etc/mtab"])

        if not os.path.isfile(var.temp + "/etc/mtab"):
            Tools.Fail("Error creating the mtab file. Exiting.")

        cls.CreateLibraryLinks()

        # Copy the init script
        Tools.SafeCopy(var.filesDirectory + "/init", var.temp)

        # Give execute permissions to the script
        cr = call(["chmod", "u+x", var.temp + "/init"])

        if cr != 0:
            Tools.Fail("Failed to give executive privileges to " + var.temp + "/init")

        # Sets initramfs script version number
        cmd = f"echo {var.version} > {var.temp}/version.bliss"
        call(cmd, shell=True)

        # Copy all of the modprobe configurations
        if os.path.isdir(var.modprobeDirectory):
            Tools.CopyTree(var.modprobeDirectory, var.temp + var.modprobeDirectory)

        cls.CopyUdevAndSupportFiles()
        cls.DumpSystemKeymap()

        # Any last substitutions or additions/modifications should be done here

        # Add any modules needed into the initramfs
        requiredModules = ",".join(Modules.GetFiles())
        cmd = f"echo {requiredModules} > {var.temp}/modules.bliss"
        call(cmd, shell=True)

        cls.CopyLibGccLibrary()

    @classmethod
    def CopyLibGccLibrary(cls):
        """Copy the 'libgcc' library so that when libpthreads loads it during runtime."""
        # https://github.com/zfsonlinux/zfs/issues/4749.

        # Find the correct path for libgcc
        libgccFilename = "libgcc_s.so"
        libgccFilenameMain = libgccFilename + ".1"

        # check for gcc-config
        gccConfigPath = Tools.GetProgramPath("gcc-config")

        if gccConfigPath:
            # Try gcc-config
            cmd = "gcc-config -L | cut -d ':' -f 1"
            res = Tools.Run(cmd)

            if res:
                # Use path from gcc-config
                libgccPath = res[0] + "/" + libgccFilenameMain
                Tools.SafeCopy(libgccPath, var.GetTempLib64Dir())
                os.chdir(var.GetTempLib64Dir())
                os.symlink(libgccFilenameMain, libgccFilename)
                return

        # Doing a 'whereis <name of libgcc library>' will not work because it seems
        # that it finds libraries in /lib, /lib64, /usr/lib, /usr/lib64, but not in
        # /usr/lib/gcc/ (x86_64-pc-linux-gnu/5.4.0, etc)

        # When a better approach is found, we can plug it in here directly and return
        # in the event that it succeeds. If it fails, we just continue execution
        # until the end of the function.

        # If we've reached this point, we have failed to copy the gcc library.
        Tools.Fail("Unable to retrieve the gcc library path!")

    @classmethod
    def CreateInitramfs(cls):
        """Create the initramfs."""
        Tools.Info("Creating the initramfs ...")

        # The find command must use the `find .` and not `find ${T}`
        # because if not, then the initramfs layout will be prefixed with
        # the ${T} path.
        os.chdir(var.temp)

        call(
            [
                "find . -print0 | cpio -o --null --format=newc | gzip -9 > "
                + var.home
                + "/"
                + var.initrd
            ],
            shell=True,
        )

        if not os.path.isfile(var.home + "/" + var.initrd):
            Tools.Fail("Error creating the initramfs. Exiting.")

    @classmethod
    def VerifyBinaries(cls):
        """Checks to see if the binaries exist, if not then emerge."""
        Tools.Info("Checking required files ...")

        # Check required base files
        cls.VerifyBinariesExist(Base.GetFiles())

        # Check required zfs files
        cls.VerifyBinariesExist(Zfs.GetFiles())

    @classmethod
    def VerifyBinariesExist(cls, vFiles):
        """Checks to see that all the binaries in the array exist and errors if they don't."""
        for file in vFiles:
            if not os.path.exists(file):
                Tools.BinaryDoesntExist(file)

    @classmethod
    def CopyBinaries(cls):
        """Copies the required files into the initramfs."""
        Tools.Info("Copying binaries ...")

        cls.FilterAndInstall(Base.GetFiles())
        cls.FilterAndInstall(Zfs.GetFiles())
        cls.FilterAndInstall(Zfs.GetOptionalFiles(), dontFail=True)

    @classmethod
    def CopyManPages(cls):
        """Copies the man pages."""
        if Zfs.IsManEnabled():
            Tools.Info("Copying man pages ...")
            cls.CopyMan(Zfs.GetManPages())

    @classmethod
    def CopyMan(cls, files):
        """Safely copies man pages if available. Will not fail."""

        # Depending the ZFS version that the user is running,
        # some manual pages that the initramfs wants to copy might not
        # have yet been written. Therefore, attempt to copy the man pages,
        # but if we are unable to copy, then just continue.
        for f in files:
            Tools.Into(f, dontFail=True)

    @classmethod
    def FilterAndInstall(cls, vFiles, **optionalArgs):
        """Filters and installs each file in the array into the initramfs.

            Optional Args:
                dontFail - Same description as the one in Tools.Copy.
        """
        for file in vFiles:
            # If the application is a binary, add it to our binary set. If the application is not
            # a binary, then we will get a CalledProcessError because the output will be null.
            try:
                check_output(
                    "file -L " + file.strip() + ' | grep "linked"',
                    shell=True,
                    universal_newlines=True,
                ).strip()
                cls._binset.add(file)
            except CalledProcessError:
                pass

            # Copy the file into the initramfs
            Tools.Into(file, dontFail=optionalArgs.get("dontFail", False))

    @classmethod
    def CopyModules(cls):
        """Copy modules and their dependencies."""
        moddeps = set()

        # Build the list of module dependencies
        Tools.Info("Copying modules ...")

        # Checks to see if all the modules in the list exist (if any)
        for file in Modules.GetFiles():
            Tools.Flag("Module: {}".format(file))
            try:
                cmd = (
                    "find "
                    + var.modules
                    + ' -iname "'
                    + file
                    + '.ko*" | grep '
                    + file
                    + ".ko"
                )
                result = check_output(cmd, universal_newlines=True, shell=True).strip()
                cls._modset.add(result)
            except CalledProcessError:
                Tools.ModuleDoesntExist(file)

        # Try to update the module dependencies database before searching it
        try:
            result = call(["depmod", var.kernel])

            if result:
                Tools.Fail("Error updating module dependency database!")
        except FileNotFoundError:
            # This should never occur because the application checks
            # that root is the user that is running the application.
            # Non-administraative users normally don't have access
            # to the 'depmod' command.
            Tools.Fail("The 'depmod' command wasn't found.")

        # Get the dependencies for all the modules in our set
        for file in cls._modset:
            # Get only the name of the module
            match = re.search("(?<=/)[a-zA-Z0-9_-]+.ko", file)

            if match:
                sFile = match.group().split(".")[0]
                cmd = (
                    "modprobe -S "
                    + var.kernel
                    + " --show-depends "
                    + sFile
                    + " | awk -F ' ' '{print $2}'"
                )
                results = check_output(cmd, shell=True, universal_newlines=True).strip()

                for i in results.split("\n"):
                    moddeps.add(i.strip())

        # Copy the modules/dependencies
        if not moddeps:
            return

        for module in moddeps:
            Tools.Into(module)

        # Update module dependency database inside the initramfs
        cls.GenerateModprobeInfo()

    @classmethod
    def CopyDependencies(cls):
        """Gets the library dependencies for all our binaries and copies them into our initramfs."""
        Tools.Info("Copying library dependencies ...")

        bindeps = set()

        # Musl and non-musl systems are supported.
        possible_libc_paths = [
            var.lib64 + "/ld-linux-x86-64.so*",
            var.lib + "/ld-musl-x86_64.so*",
        ]
        libc_found = False

        for libc in possible_libc_paths:
            try:
                # (Dirty implementation) Use the exit code of grep with no messages being outputed to see if this interpreter exists.
                # We don't know the name yet which is why we are using the wildcard in the variable declaration.
                result = call("grep -Uqs thiswillnevermatch " + libc, shell=True)

                # 0 = match found
                # 1 = file exists but not found
                # 2 = file doesn't exist
                # In situations 0 or 1, we are good, since we just care that the file exists.
                if result != 0 and result != 1:
                    continue

                # Get the interpreter name that is on this system
                result = check_output(
                    "ls " + libc, shell=True, universal_newlines=True
                ).strip()

                # Add intepreter to deps since everything will depend on it
                bindeps.add(result)
                libc_found = True
            except Exception as e:
                pass

        if not libc_found:
            Tools.Fail("No libc interpreters were found!")

        # Get the dependencies for the binaries we've collected and add them to
        # our bindeps set. These will all be copied into the initramfs later.
        for binary in cls._binset:
            cmd = (
                "ldd "
                + binary
                + " 2>&1 | grep -v 'not a dynamic executable' | awk -F '=>' '{print $2}' | awk -F ' ' '{print $1}' | sed '/^ *$/d'"
            )
            results = check_output(cmd, shell=True, universal_newlines=True).strip()

            if results:
                for library in results.split("\n"):
                    bindeps.add(library)

        # Copy all the dependencies of the binary files into the initramfs
        for library in bindeps:
            Tools.Into(library)