This note is to inform the community about work in progress. As part of the the Live USB customization project work, I've filed several patches to this ticket, https://bugzilla.redhat.com/show_bug.cgi?id=448030, RFE: create a bootable Live USB stick from the running livecd.
I'm currently working on a new version of the new, edit-livecd tool, http://git.fedorahosted.org/git/?p=livecd;a=blob_plain;f=tools/edit-livecd;hb=HEAD, I've renamed to edit-liveos. The goal is to support cloning of an existing image along with resparsing the operating system so that the overlay can be reset and a livecd.iso file could be generated, if desired. The attached edit-liveos and .diff files will show the approach and current state of development. Due to bugs in the shutil.copytree() function, I've used rsync to copy the filesystem. This seems fast and allows greater customization, if desired, by use of per directory .rsync-filter files. This is my first python project, so right now I'm learning how to work with the various filesystem image & mount objects and methods. I've also attached the edit-liveos and .diff files to ticket # 448030. Comments or suggestions are welcome. --Fred
diff --git a/tools/edit-livecd b/tools/edit-livecd old mode 100755 new mode 100644 index 809b015..be6baed --- a/tools/edit-livecd +++ b/tools/edit-livecd @@ -1,6 +1,6 @@ #!/usr/bin/python -tt # -# edit livecd: Edit a livecd to insert files +# edit-liveos: Edit a LiveOS to insert files # # Copyright 2009, Red Hat Inc. # Written by Perry Myers <pmyers at redhat.com> & David Huff <dhuff at redhat.com> @@ -21,6 +21,7 @@ import os import sys +import stat import tempfile import shutil import subprocess @@ -41,11 +42,11 @@ class ExistingSparseLoopbackDisk(SparseLoopbackDisk): LoopbackDisk.create(self) class LiveImageEditor(LiveImageCreator): - """class for editing LiveCD images. + """class for editing LiveOS images. We need an instance of LiveImageCreator however we do not have a kickstart - file nor do we need to create a new image. We just want to reuse some of - LiveImageCreators methods on an existing livecd image. + file and we may not need to create a new image. We just want to reuse some of + LiveImageCreators methods on an existing LiveOS image. """ @@ -61,6 +62,15 @@ class LiveImageEditor(LiveImageCreator): self.tmpdir = "/var/tmp" """The directory in which all temporary files will be created.""" + self.clone = False + """Signals when to copy an attached LiveOS image as base.""" + + self._include = None + """A string of file or directory paths to include in __copy_img_root.""" + + self._builder = "someone" + """The name of the Remix builder for _branding.""" + self.skip_compression = False """Controls whether to use squashfs to compress the image.""" @@ -97,8 +107,14 @@ class LiveImageEditor(LiveImageCreator): def __get_image(self): if self._LoopImageCreator__imagedir is None: self.__ensure_builddir() - self._LoopImageCreator__imagedir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir), prefix = self.name + "-") - return self._LoopImageCreator__imagedir + "/ext3fs.img" + self._LoopImageCreator__imagedir = \ + tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir), + prefix = self.name + "-") + if self.clone: + rtn = self._LoopImageCreator__imagedir + else: + rtn = self._LoopImageCreator__imagedir + "/ext3fs.img" + return rtn _image = property(__get_image) """The location of the image file""" @@ -117,14 +133,14 @@ class LiveImageEditor(LiveImageCreator): raise CreatorError("Failed to determine fsimage TYPE: %s" % e ) - def _get_fslable(self): + def _get_fslabel(self): dev_null = os.open("/dev/null", os.O_WRONLY) try: out = subprocess.Popen(["/sbin/e2label", self._image], stdout = subprocess.PIPE, stderr = dev_null).communicate()[0] - self._LoopImageCreator__fslable = out.strip() + self._LoopImageCreator__fslabel = out.strip() except IOError, e: raise CreatorError("Failed to determine fsimage TYPE: %s" % e ) @@ -136,7 +152,7 @@ class LiveImageEditor(LiveImageCreator): try: self._ImageCreator__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir), - prefix = "edit-livecd-") + prefix = "edit-liveos-") except OSError, (err, msg): raise CreatorError("Failed create build directory in %s: %s" % (self.tmpdir, msg)) @@ -162,21 +178,25 @@ class LiveImageEditor(LiveImageCreator): finally: os.unlink(path) - def mount(self, base_on, cachedir = None): """mount existing file system. we have to override mount b/c we are not creating an new install root - nor do we need to setup the file system, ie makedirs(/etc/, /boot, ...), + nor do we need to setup the file system, i.e., makedirs(/etc/, /boot, ...), nor do we want to overwrite fstab, or create selinuxfs We also need to get some info about the image before we can mount it. + base_on -- the <LIVEIMG.src> a LiveOS.iso file or attached LiveOS device, + such as, /dev/live for a currently running image. + + cachedir -- a directory in which to store a Yum cache; Not used in edit-liveos. + """ if not base_on: - raise CreatorError("No base livecd image specified") + raise CreatorError("No base LiveOS image specified.") self.__ensure_builddir() @@ -188,19 +208,33 @@ class LiveImageEditor(LiveImageCreator): makedirs(self._LoopImageCreator__imagedir) makedirs(self._ImageCreator_outdir) - LiveImageCreator._base_on(self, base_on) + if self.clone: + self._base_on(base_on) + else: + LiveImageCreator._base_on(self, base_on) self._LoopImageCreator__image_size = os.stat(self._image)[stat.ST_SIZE] + self._get_fstype() - self._get_fslable() - self.fslabel = self._LoopImageCreator__fslable - - self._LoopImageCreator__instloop = ExtDiskMount(ExistingSparseLoopbackDisk(self._image, - self._LoopImageCreator__image_size), - self._ImageCreator_instroot, - self._fstype, - self._LoopImageCreator__blocksize, - self.fslabel) + self._get_fslabel() + self.fslabel = self._LoopImageCreator__fslabel + + if self.clone: + self._LoopImageCreator__instloop = \ + ExtDiskMount(self._image, + self._LoopImageCreator__image_size, + self._ImageCreator_instroot, + self._fstype, + self._LoopImageCreator__blocksize, + self.fslabel) + else: + self._LoopImageCreator__instloop = \ + ExtDiskMount(ExistingSparseLoopbackDisk(self._image, + self._LoopImageCreator__image_size), + self._ImageCreator_instroot, + self._fstype, + self._LoopImageCreator__blocksize, + self.fslabel) try: self._LoopImageCreator__instloop.mount() except MountError, e: @@ -219,46 +253,174 @@ class LiveImageEditor(LiveImageCreator): os.symlink("../proc/mounts", self._instroot + "/etc/mtab") - self.__copy_cd_root(base_on) + self.__copy_img_root(base_on) + + def _base_on(self, base_on): + """clone the running LiveOS image as the basis for the new image.""" + +# imgmnt = DiskMount(RawDisk(base_on, 0), self._mkdtemp()) +# , self._LoopImageCreator__imagedir) + +# try: +# imgmnt.mount() + +# except MountError, e: +# raise CreatorError("Failed to mount '%s' : %s" % +# (base_on, e)) + + subprocess.call(["rsync", "-ptgorlHASx", "--specials", "--progress", + "--exclude", "dev", + "--exclude", "proc", + "--exclude", "media", + "--exclude", "mnt", + "--exclude", "sys", + "--exclude", ".liveimg*", + "--exclude", "tmp", + "/", self._LoopImageCreator__imagedir]) + + def _mount_instroot(self, base_on = None): + self.__imgdir = self._mkdtemp() + + self.__instloop = ExtDiskMount(self._image, + self._instroot, + self.__fstype, + self.__blocksize, + self.fslabel) + + try: + self.__instloop.mount() + except MountError, e: + raise CreatorError("Failed to mount '%s' : %s" % + (self._image, e)) + def __copy_img_root(self, base_on): + """helper function to copy root content of the base LiveIMG to ISOdir""" - def __copy_cd_root(self, base_on): - """helper function to root content of the base liveCD to ISOdir""" + ignore_list = ['squashfs.img', 'osmin.img', 'home.img', 'overlay-*'] + + if self.clone: + imgmnt = DiskMount(base_on, self._mkdtemp()) + ignore_list.remove('home.img') + else: + imgmnt = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp()) + print imgmnt.disk + print imgmnt.mountdir + print imgmnt.fstype + print imgmnt.rmmountdir + exit() - isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp()) self._LiveImageCreatorBase__isodir = self._ImageCreator__builddir + "/iso" try: - isoloop.mount() + imgmnt.mount() # legacy LiveOS filesystem layout support, remove for F9 or F10 - if os.path.exists(isoloop.mountdir + "/squashfs.img"): - squashimg = isoloop.mountdir + "/squashfs.img" + if os.path.exists(imgmnt.mountdir + "/squashfs.img"): + squashimg = imgmnt.mountdir + "/squashfs.img" else: - squashimg = isoloop.mountdir + "/LiveOS/squashfs.img" - - #copy over everything but squashimg - shutil.copytree(isoloop.mountdir, - self._LiveImageCreatorBase__isodir, - ignore=shutil.ignore_patterns("squashfs.img", "osmin.img")) + squashimg = imgmnt.mountdir + "/LiveOS/squashfs.img" + + # include specified files or directories + if self._include: + dst = self._LiveImageCreatorBase__isodir + print self._include + for fd in self._include: + if os.path.isfile(fd): + shutil.copy2(fd, os.path.join(dst, name)) + elif os.path.isdir(fd): + shutil.copytree(fd, + self._LiveImageCreatorBase__isodir) + else: + #copy over everything but squashimg + shutil.copytree(imgmnt.mountdir, + self._LiveImageCreatorBase__isodir, + ignore=shutil.ignore_patterns(ignore_list) except MountError, e: - raise CreatorError("Failed to loopback mount '%s' : %s" % + raise CreatorError("Failed to mount '%s' : %s" % (base_on, e)) finally: - isoloop.cleanup() + imgmnt.cleanup() + + def _brand (self, _LiveImageCreatorBase__isodir, _instroot): + + # Get build name from boot configuration file. + try: + cfgf = open(_LiveImageCreatorBase__isodir + + "/syslinux/syslinux.cfg", "r") + + except IOError: + cfgf = open(_LiveImageCreatorBase__isodir + + "/syslinux/extlinux.conf", "r") + for line in cfgf: + i = line.find("Welcome to ") + if i > -1: + self.name = line[i+11:-2] + break + cfgf.close() + + # Update fedora-release message with Remix details. + try: + text = open(_instroot + "/etc/fedora-release", "r").read() + except IOError: + raise CreatorError("Failed to open '%s' : %s" % + (cfgf, e)) + + dt = datetime.strptime(datetime.now(), "%d-%b-%Y") + text = text + " Remix of %(name)s by %(_builder)s, packaged on %(dt)s" + open(self._instroot + "/etc/fedora-release", "w").write(text) + + def __get_basic_syslinux_config(self, **args): + dt = datetime.strptime(datetime.now(), "%d-%b-%Y") + return """ +default %(menu)s +timeout %(timeout)d + +%(background)s +menu title Welcome to %(name)s %(dt)s Remix! +menu color border 0 #ffffffff #00000000 +menu color sel 7 #ffffffff #ff000000 +menu color title 0 #ffffffff #00000000 +menu color tabmsg 0 #ffffffff #00000000 +menu color unsel 0 #ffffffff #00000000 +menu color hotsel 0 #ff000000 #ffffffff +menu color hotkey 7 #ffffffff #ff000000 +menu color timeout_msg 0 #ffffffff #00000000 +menu color timeout 0 #ffffffff #00000000 +menu color cmdline 0 #ffffffff #00000000 +menu hidden +menu hiddenrow 5 +""" % args def parse_options(args): - parser = optparse.OptionParser(usage = "%prog [-s=<script.sh>] <LIVECD.iso>") + parser = optparse.OptionParser(usage = "%prog [-n=<name>] \ + \n\r [-o=<output>] \ + \n\r [-s=<script.sh>] \ + \n\r [-t=<tmpdir>] \ + \n\r [-i=<includes>] \ + \n\r [--builder] \ + \n\r [--clone] \ + \n\r [--skip-compression] \ + \n\r [--skip-minimize] \ + \n\r <LIVEIMG.src>") parser.add_option("-n", "--name", type="string", dest="name", - help="name of new livecd (don't include .iso will be added)") + help="name of new LiveOS (don't include .iso, it will be added)") parser.add_option("-o", "--output", type="string", dest="output", help="specify the output dir") parser.add_option("-s", "--script", type="string", dest="script", - help="specify script to run chrooted in the livecd fsimage") + help="specify script to run chrooted in the LiveOS fsimage") + + parser.add_option("", "--clone", action="store_true", dest="clone", + help="Specify that source image is LiveOS block device.") + + parser.add_option("-i", "--include", type="string", dest="include", + help="Specify directory or file patterns to be included in copy_img_root.") + + parser.add_option("", "--builder", type="string", dest="builder", + help="Specify the builder of a Remix.") parser.add_option("-t", "--tmpdir", type="string", dest="tmpdir", default="/var/tmp", @@ -276,6 +438,9 @@ def parse_options(args): parser.print_usage() sys.exit(1) + if stat.S_ISBLK(os.stat(args[0]).st_mode): + options.clone = True + return (args[0], options) def rebuild_iso_symlinks(isodir): @@ -285,37 +450,42 @@ def rebuild_iso_symlinks(isodir): efi_initrd = "%s/EFI/boot/initrd0.img" % isodir isolinux_initrd = "%s/isolinux/initrd0.img" % isodir - os.remove(efi_vmlinuz) - os.remove(efi_initrd) - os.symlink(isolinux_vmlinuz,efi_vmlinuz) - os.symlink(isolinux_initrd,efi_initrd) + if os.path.exists(efi_vmlinuz): + os.remove(efi_vmlinuz) + os.remove(efi_initrd) + os.symlink(isolinux_vmlinuz,efi_vmlinuz) + os.symlink(isolinux_initrd,efi_initrd) def main(): - (livecd, options) = parse_options(sys.argv[1:]) + (LiveOS, options) = parse_options(sys.argv[1:]) if os.geteuid () != 0: - print >> sys.stderr, "You must run edit-livecd as root" + print >> sys.stderr, "You must run edit-liveos as root" return 1 - if options.name: + if options.name and options.name != os.path.basename(LiveOS): name = options.name else: - name = os.path.basename(livecd) + ".edited" + name = os.path.basename(LiveOS) + ".edited" if options.output: output = options.output else: - output = os.path.dirname(livecd) - + output = os.path.dirname(LiveOS) editor = LiveImageEditor(name) + editor._include = options.include + editor.clone = options.clone editor.tmpdir = os.path.abspath(options.tmpdir) + editor._builder = options.builder editor.skip_compression = options.skip_compression editor.skip_minimize = options.skip_minimize try: - editor.mount(livecd, cachedir = None) + editor.mount(LiveOS, cachedir = None) + if editor.clone: + editor._brand(editor._LiveImageCreatorBase__isodir, self.__instloop) if options.script: print "Running edit script '%s'" % options.script editor._run_script(options.script) @@ -327,7 +497,7 @@ def main(): editor.unmount() editor.package(output) except CreatorError, e: - logging.error(u"Error editing Live CD : %s" % e) + logging.error(u"Error editing LiveOS : %s" % e) return 1 finally: editor.cleanup() @@ -338,7 +508,6 @@ def main(): if __name__ == "__main__": sys.exit(main()) - arch = rpmUtils.arch.getBaseArch() if arch in ("i386", "x86_64"): LiveImageCreator = x86LiveImageCreator
edit-liveos
Description: Binary data
_______________________________________________ SoaS mailing list SoaS@lists.sugarlabs.org http://lists.sugarlabs.org/listinfo/soas