[Fedora-livecd-list] [PATCH] add edit-livecd script for editing livecd based images

David Huff dhuff at redhat.com
Thu Mar 11 14:49:07 UTC 2010


Is there any objections to including this script in livecd-toosls?  We 
have seen a good amount of interest in something like this.

-D


On 03/09/2010 04:25 PM, Joey Boggs wrote:
> Resubmitting the edit-livecd script with the latest updates.
>
> edit-livecd is used for opening up livecd images to add and/or edit files with out having to rebuild the iso image.
>
> This script is useful for people who don't have the development and yum infrastructure/repos set up to build livecd's from scratch. This is especially useful for custom livecd images, like the ovirt-node project[1].
>
> We agree that changes to packaging and binaries should always be done by modifying kickstart and rebuilding the image via livecd-creator.  But if all you want to do is add/edit a config file, public key for SSH, or change the root password this can be done with the edit-livecd script.
>
> This is the second generation of the edit-livecd tool that replaces our original bash script with a python version and aims to reuse as much as the livecd-creator libraries as possible.  The main reason for the new creator class is to avoid the need for a kisckstart file in order to edit an existing livecd image.
>
> [1] http://ovirt.org
>
> ---
>   Makefile          |    2 +
>   tools/edit-livecd |  350 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 352 insertions(+), 0 deletions(-)
>   create mode 100755 tools/edit-livecd
>
> diff --git a/Makefile b/Makefile
> index 991b27b..62e7fd8 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -25,6 +25,7 @@ install: man
>   	$(INSTALL_PROGRAM) -D tools/image-creator $(DESTDIR)/usr/bin/image-creator
>   	$(INSTALL_PROGRAM) -D tools/livecd-iso-to-disk.sh $(DESTDIR)/usr/bin/livecd-iso-to-disk
>   	$(INSTALL_PROGRAM) -D tools/livecd-iso-to-pxeboot.sh $(DESTDIR)/usr/bin/livecd-iso-to-pxeboot
> +	$(INSTALL_PROGRAM) -D tools/edit-livecd $(DESTDIR)/usr/bin/edit-livecd
>   	$(INSTALL_DATA) -D AUTHORS $(DESTDIR)/usr/share/doc/livecd-tools-$(VERSION)/AUTHORS
>   	$(INSTALL_DATA) -D COPYING $(DESTDIR)/usr/share/doc/livecd-tools-$(VERSION)/COPYING
>   	$(INSTALL_DATA) -D README $(DESTDIR)/usr/share/doc/livecd-tools-$(VERSION)/README
> @@ -40,6 +41,7 @@ uninstall:
>   	rm -f $(DESTDIR)/usr/bin/livecd-creator
>   	rm -rf $(DESTDIR)/usr/lib/livecd-creator
>   	rm -rf $(DESTDIR)/usr/share/doc/livecd-tools-$(VERSION)
> +	rm -f $(DESTDIR)/usr/bin/edit-livecd
>
>   dist : all
>   	git archive --format=tar --prefix=livecd-tools-$(VERSION)/ HEAD | bzip2 -9v>  livecd-tools-$(VERSION).tar.bz2
> diff --git a/tools/edit-livecd b/tools/edit-livecd
> new file mode 100755
> index 0000000..920522c
> --- /dev/null
> +++ b/tools/edit-livecd
> @@ -0,0 +1,350 @@
> +#!/usr/bin/python -tt
> +#
> +# edit livecd: Edit a livecd to insert files
> +#
> +# Copyright 2009, Red Hat  Inc.
> +# Written by Perry Myers<pmyers at redhat.com>  &  David Huff<dhuff at redhat.com>
> +#
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; version 2 of the License.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU Library General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, write to the Free Software
> +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
> +
> +import os
> +import sys
> +import tempfile
> +import shutil
> +import subprocess
> +import optparse
> +import logging
> +
> +from imgcreate.debug import *
> +from imgcreate.fs import *
> +from imgcreate.live import *
> +
> +class ExistingSparseLoopbackDisk(SparseLoopbackDisk):
> +    """don't want to expand the disk"""
> +    def __init__(self, lofile, size):
> +        SparseLoopbackDisk.__init__(self, lofile, size)
> +
> +    def create(self):
> +        #self.expand(create = True)
> +        LoopbackDisk.create(self)
> +
> +class LiveImageEditor(LiveImageCreator):
> +    """class for editing LiveCD 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.
> +
> +    """
> +
> +    def __init__(self, name):
> +        """Initialize a LiveImageEditor instance.
> +
> +        creates a dummy instance of LiveImageCreator
> +        We do not initialize any sub classes b/c we have no ks file.
> +
> +        """
> +        self.name = name
> +
> +        self.tmpdir = "/var/tmp"
> +        """The directory in which all temporary files will be created."""
> +
> +        self.skip_compression = False
> +        """Controls whether to use squashfs to compress the image."""
> +
> +        self.skip_minimize = False
> +        """Controls whether an image minimizing snapshot should be created."""
> +
> +        self._isofstype = "iso9660"
> +        self.__isodir = None
> +
> +        self._ImageCreator__builddir = None
> +        """working directory"""
> +
> +        self._ImageCreator_instroot = None
> +        """where the extfs.img is mounted for modification"""
> +
> +        self._ImageCreator_outdir = None
> +        """where final iso gets written"""
> +
> +        self._ImageCreator__bindmounts = []
> +
> +        self._LoopImageCreator__imagedir = None
> +        """dir for the extfs.img"""
> +
> +        self._LoopImageCreator__blocksize = 4096
> +        self._LoopImageCreator__fslabel = None
> +        self._LoopImageCreator__instloop = None
> +        self._LoopImageCreator__fstype = None
> +        self._LoopImageCreator__image_size = None
> +
> +        self._LiveImageCreatorBase__isodir = None
> +        """directory where the iso is staged"""
> +
> +    # properties
> +    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"
> +    _image = property(__get_image)
> +    """The location of the image file"""
> +
> +
> +    def _get_fstype(self):
> +        dev_null = os.open("/dev/null", os.O_WRONLY)
> +        try:
> +            out = subprocess.Popen(["/sbin/blkid", self._image],
> +                                   stdout = subprocess.PIPE,
> +                                   stderr = dev_null).communicate()[0]
> +            for word in out.split():
> +                if word.startswith("TYPE"):
> +                    self._LoopImageCreator__fstype = word.split("=")[1].strip("\"")
> +
> +        except IOError, e:
> +            raise CreatorError("Failed to determine fsimage TYPE: %s" % e )
> +
> +
> +    def _get_fslable(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()
> +
> +        except IOError, e:
> +            raise CreatorError("Failed to determine fsimage TYPE: %s" % e )
> +
> +
> +    def __ensure_builddir(self):
> +        if not self._ImageCreator__builddir is None:
> +            return
> +
> +        try:
> +            self._ImageCreator__builddir = tempfile.mkdtemp(dir =  os.path.abspath(self.tmpdir),
> +                                               prefix = "edit-livecd-")
> +        except OSError, (err, msg):
> +            raise CreatorError("Failed create build directory in %s: %s" %
> +                               (self.tmpdir, msg))
> +
> +
> +    def _run_script(self, script):
> +
> +        (fd, path) = tempfile.mkstemp(prefix = "script-",
> +                                          dir = self._instroot + "/tmp")
> +
> +        logging.debug("copying script to install root: %s" % path)
> +        shutil.copy(os.path.abspath(script), path)
> +        os.close(fd)
> +        os.chmod(path, 0700)
> +
> +        script = "/tmp/" + os.path.basename(path)
> +
> +
> +        try:
> +            subprocess.call([script], preexec_fn = self._chroot)
> +        except OSError, e:
> +            raise CreatorError("Failed to execute script %s, %s " % (script, e))
> +        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 want to overwrite fstab, or create selinuxfs
> +
> +        We also need to get some info about the image before we
> +        can mount it.
> +
> +        """
> +
> +        if not base_on:
> +            raise CreatorError("No base livecd image specified")
> +
> +        self.__ensure_builddir()
> +
> +        self._ImageCreator_instroot = self._ImageCreator__builddir + "/install_root"
> +        self._LoopImageCreator__imagedir = self._ImageCreator__builddir + "/ex"
> +        self._ImageCreator_outdir = self._ImageCreator__builddir + "/out"
> +
> +        makedirs(self._ImageCreator_instroot)
> +        makedirs(self._LoopImageCreator__imagedir)
> +        makedirs(self._ImageCreator_outdir)
> +
> +        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)
> +        try:
> +            self._LoopImageCreator__instloop.mount()
> +        except MountError, e:
> +            raise CreatorError("Failed to loopback mount '%s' : %s" %
> +                               (self._image, e))
> +
> +        cachesrc = cachedir or (self._ImageCreator__builddir + "/yum-cache")
> +        makedirs(cachesrc)
> +
> +        for (f, dest) in [("/sys", None), ("/proc", None),
> +                          ("/dev/pts", None), ("/dev/shm", None),
> +                          (cachesrc, "/var/cache/yum")]:
> +            self._ImageCreator__bindmounts.append(BindChrootMount(f, self._instroot, dest))
> +
> +        self._do_bindmounts()
> +
> +        os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
> +
> +        self.__copy_cd_root(base_on)
> +
> +
> +    def __copy_cd_root(self, base_on):
> +        """helper function to root content of the base liveCD to ISOdir"""
> +
> +        isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())
> +        self._LiveImageCreatorBase__isodir = self._ImageCreator__builddir + "/iso"
> +
> +        try:
> +            isoloop.mount()
> +            # legacy LiveOS filesystem layout support, remove for F9 or F10
> +            if os.path.exists(isoloop.mountdir + "/squashfs.img"):
> +                squashimg = isoloop.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"))
> +        except MountError, e:
> +            raise CreatorError("Failed to loopback mount '%s' : %s" %
> +                               (base_on, e))
> +
> +        finally:
> +            isoloop.cleanup()
> +
> +
> +def parse_options(args):
> +    parser = optparse.OptionParser(usage = "%prog [-s=<script.sh>]<LIVECD.iso>")
> +
> +    parser.add_option("-n", "--name", type="string", dest="name",
> +                      help="name of new livecd (don't include .iso 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")
> +
> +    parser.add_option("-t", "--tmpdir", type="string",
> +                      dest="tmpdir", default="/var/tmp",
> +                      help="Temporary directory to use (default: /var/tmp)")
> +
> +    parser.add_option("", "--skip-compression", action="store_true", dest="skip_compression")
> +
> +    parser.add_option("", "--skip-minimize", action="store_true", dest="skip_minimize")
> +
> +    setup_logging(parser)
> +
> +    (options, args) = parser.parse_args()
> +
> +    if len(args) != 1:
> +        parser.print_usage()
> +        sys.exit(1)
> +
> +    return (args[0], options)
> +
> +def rebuild_iso_symlinks(isodir):
> +    # remove duplicate files and rebuild symlinks to reduce iso size
> +    efi_vmlinuz = "%s/EFI/boot/vmlinuz0" % isodir
> +    isolinux_vmlinuz = "%s/isolinux/vmlinuz0" % 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)
> +
> +
> +def main():
> +    (livecd, options) = parse_options(sys.argv[1:])
> +
> +    if os.geteuid () != 0:
> +        print>>  sys.stderr, "You must run edit-livecd as root"
> +        return 1
> +
> +    if options.name:
> +        name = options.name
> +    else:
> +        name = os.path.basename(livecd) + ".edited"
> +
> +    if options.output:
> +        output = options.output
> +    else:
> +        output = os.path.dirname(livecd)
> +
> +
> +    editor = LiveImageEditor(name)
> +    editor.tmpdir = os.path.abspath(options.tmpdir)
> +    editor.skip_compression = options.skip_compression
> +    editor.skip_minimize = options.skip_minimize
> +
> +    try:
> +        editor.mount(livecd, cachedir = None)
> +        if options.script:
> +            print "Running edit script '%s'" % options.script
> +            editor._run_script(options.script)
> +        else:
> +            print "Launching shell. Exit to continue."
> +            print "----------------------------------"
> +            editor.launch_shell()
> +        rebuild_iso_symlinks(editor._LiveImageCreatorBase__isodir)
> +        editor.unmount()
> +        editor.package(output)
> +    except CreatorError, e:
> +        logging.error(u"Error editing Live CD : %s" % e)
> +        return 1
> +    finally:
> +        editor.cleanup()
> +
> +    return 0
> +
> +
> +if __name__ == "__main__":
> +    sys.exit(main())
> +
> +
> +arch = rpmUtils.arch.getBaseArch()
> +if arch in ("i386", "x86_64"):
> +    LiveImageCreator = x86LiveImageCreator
> +elif arch in ("ppc",):
> +    LiveImageCreator = ppcLiveImageCreator
> +elif arch in ("ppc64",):
> +    LiveImageCreator = ppc64LiveImageCreator
> +else:
> +    raise CreatorError("Architecture not supported!")



More information about the livecd mailing list