From d0960bba146f4066ede027a9ea12368ab21ae423 Mon Sep 17 00:00:00 2001 From: Will Woods Date: Thu, 9 Jun 2011 13:39:28 -0400 Subject: [PATCH] Add 'squashfs' compression type This adds the 'squashfs' compression type, which builds runtime images that stay compressed in RAM. It accomplishes this by building the images almost exactly like the Live images are built: 1) Create an empty ext4 filesystem on a large sparse file 2) Copy the runtime files into the ext4 filesystem 3) Place the ext4 image at "LiveOS/rootfs.img" 4) Create a squashfs.img which contains LiveOS/rootfs.img To make this bootable, we need dracut's startup scripts. So before creating the runtime image, we make a dracut initramfs.img by chrooting into the runtime and running dracut. Finally, we add squashfs.img to initramfs.img, along with an extra file (/etc/cmdline) which directs dracut to use /squashfs.img as its root device. And there we go! Easy, right?! --- share/ramdisk.ltmpl | 1 + src/pylorax/__init__.py | 8 ++- src/pylorax/constants.py | 2 + src/pylorax/installtree.py | 99 +++++++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/share/ramdisk.ltmpl b/share/ramdisk.ltmpl index a8da0155..1f73a879 100644 --- a/share/ramdisk.ltmpl +++ b/share/ramdisk.ltmpl @@ -161,6 +161,7 @@ install "xorg-x11-fonts-ethiopic" install "xorg-x11-fonts-misc" install "xorg-x11-server-Xorg" install "xorg-x11-server-utils" +install "xz" install "yum-langpacks" install "${product}-logos" install "${product}-release" diff --git a/src/pylorax/__init__.py b/src/pylorax/__init__.py index d5da4296..ff9bf3bb 100644 --- a/src/pylorax/__init__.py +++ b/src/pylorax/__init__.py @@ -102,8 +102,8 @@ class Lorax(BaseLoraxClass): self.conf.set("yum", "skipbroken", "0") self.conf.add_section("compression") - self.conf.set("compression", "type", "xz") - self.conf.set("compression", "args", "-9") + self.conf.set("compression", "type", "squashfs") + self.conf.set("compression", "args", "-comp xz") # read the config file if os.path.isfile(conf_file): @@ -293,6 +293,10 @@ class Lorax(BaseLoraxClass): logger.info("moving stubs") self.installtree.move_stubs() + if self.conf.get("compression", "type") == "squashfs": + # create dracut initramfs (before stuff gets shuffled/removed) + self.installtree.make_dracut_initramfs() + # get the list of required modules logger.info("getting list of required modules") modules = [f[1:] for f in template if f[0] == "module"] diff --git a/src/pylorax/constants.py b/src/pylorax/constants.py index c37f0db9..09467412 100644 --- a/src/pylorax/constants.py +++ b/src/pylorax/constants.py @@ -48,6 +48,8 @@ class LoraxRequiredCommands(dict): self["LOSETUP"] = "losetup" self["MKDOSFS"] = "mkdosfs" self["MKISOFS"] = "mkisofs" + self["MKFS_EXT4"] = "mkfs.ext4" + self["MKSQUASHFS"] = "mksquashfs" self["MODINFO"] = "modinfo" self["MOUNT"] = "mount" self["PARTED"] = "parted" diff --git a/src/pylorax/installtree.py b/src/pylorax/installtree.py index c3bfda4e..0e49213b 100644 --- a/src/pylorax/installtree.py +++ b/src/pylorax/installtree.py @@ -46,6 +46,7 @@ class LoraxInstallTree(BaseLoraxClass): self.basearch = basearch self.libdir = libdir self.workdir = workdir + self.initramfs = None self.lcmds = constants.LoraxRequiredCommands() @@ -512,7 +513,10 @@ class LoraxInstallTree(BaseLoraxClass): shutil.move(joinpaths(self.workdir, kernel.version), joinpaths(self.root, "modules")) - self.make_initramfs_runtime(initrd, kernel, type, args) + if type == "squashfs": + self.make_squashfs_runtime(initrd, kernel, type, args) + else: + self.make_initramfs_runtime(initrd, kernel, type, args) # move modules out of the tree again logger.debug("moving modules outside initrd") @@ -543,6 +547,99 @@ class LoraxInstallTree(BaseLoraxClass): logger.debug("compressing") rc = compressed.wait() + def make_dracut_initramfs(self): + outfile = "/tmp/initramfs.img" # inside the chroot + logger.debug("chrooting into installtree to create initramfs.img") + subprocess.check_call(["chroot", self.root, + "/sbin/dracut", "--nomdadmconf", "--nolvmconf", + "--xz", "--modules", "base dmsquash-live", + outfile, self.kernels[0].version]) + # move output file into installtree workdir + self.initramfs = joinpaths(self.workdir, "initramfs.img") + shutil.move(joinpaths(self.root, outfile), self.initramfs) + + def make_squashfs_runtime(self, runtime, kernel, type, args): + """This is a little complicated, but dracut wants to find a squashfs + image named "squashfs.img" which contains a filesystem image named + "LiveOS/rootfs.img". + Placing squashfs.img inside a cpio image and concatenating that + with the existing initramfs.img will make squashfs.img appear inside + initramfs at boot time. + """ + # Check to be sure we have a dracut initramfs to use + assert self.initramfs, "make_dracut_initramfs has not been run!" + + # These exact names are required by dracut + squashname = "squashfs.img" + imgname = "LiveOS/rootfs.img" + + # Create fs image of installtree (2GB sparse file) + fsimage = joinpaths(self.workdir, "installtree.img") + open(fsimage, "wb").truncate(2*1024**3) + mountpoint = joinpaths(self.workdir, "rootfs") + os.mkdir(mountpoint, 0755) + mkfs = [self.lcmds.MKFS_EXT4, "-q", "-L", "Anaconda", "-F", fsimage] + logger.debug("formatting rootfs image: %s" % " ".join(mkfs)) + subprocess.check_call(mkfs, stdout=subprocess.PIPE) + logger.debug("mounting rootfs image at %s", mountpoint) + subprocess.check_call([self.lcmds.MOUNT, "-o", "loop", + fsimage, mountpoint]) + try: + logger.info("copying installtree into rootfs image") + srcfiles = [joinpaths(self.root, f) for f in os.listdir(self.root)] + subprocess.check_call(["cp", "-a"] + srcfiles + [mountpoint]) + finally: + logger.debug("unmounting rootfs image") + rc = subprocess.call([self.lcmds.UMOUNT, mountpoint]) + if rc != 0: + logger.critical("umount %s failed (returncode %i)", mountpoint, rc) + sys.exit(rc) + os.rmdir(mountpoint) + + # Make squashfs with rootfs image inside + logger.info("creating %s containing %s", squashname, imgname) + squashtree = joinpaths(self.workdir, "squashfs") + os.makedirs(joinpaths(squashtree, os.path.dirname(imgname))) + shutil.move(fsimage, joinpaths(squashtree, imgname)) + squashimage = joinpaths(self.workdir, squashname) + cmd = [self.lcmds.MKSQUASHFS, squashtree, squashimage] + args.split() + subprocess.check_call(cmd) + shutil.rmtree(squashtree) + + # Put squashimage in a new initramfs image with dracut config + logger.debug("creating initramfs image containing %s", squashname) + initramfsdir = joinpaths(self.workdir, "initramfs") + # write boot cmdline for dracut + cmdline = joinpaths(initramfsdir, "etc/cmdline") + os.makedirs(os.path.dirname(cmdline)) + with open(cmdline, "wb") as fobj: + fobj.write("root=live:/{0}\n".format(squashname)) + # add squashimage to new cpio image + shutil.move(squashimage, initramfsdir) + # create cpio container + squash_cpio = joinpaths(self.workdir, "squashfs.cpio") + chdir = lambda: os.chdir(initramfsdir) + find = subprocess.Popen([self.lcmds.FIND, "."], stdout=subprocess.PIPE, + preexec_fn=chdir) + cpio = subprocess.Popen([self.lcmds.CPIO, "--quiet", "-c", "-o"], + stdin=find.stdout, + stdout=open(squash_cpio, "wb"), + preexec_fn=chdir) + cpio.communicate() + shutil.rmtree(initramfsdir) + + # create final image + logger.debug("concatenating dracut initramfs and squashfs initramfs") + logger.debug("initramfs.img size = %i", os.stat(self.initramfs).st_size) + with open(runtime.fpath, "wb") as output: + for f in self.initramfs, squash_cpio: + with open(f, "rb") as fobj: + data = fobj.read(4096) + while data: + output.write(data) + data = fobj.read(4096) + os.remove(self.initramfs) + os.remove(squash_cpio) @property def kernels(self):