diff options
Diffstat (limited to 'scripts/lib/mic/imager')
-rw-r--r-- | scripts/lib/mic/imager/__init__.py | 0 | ||||
-rw-r--r-- | scripts/lib/mic/imager/baseimager.py | 1335 | ||||
-rw-r--r-- | scripts/lib/mic/imager/fs.py | 99 | ||||
-rw-r--r-- | scripts/lib/mic/imager/livecd.py | 750 | ||||
-rw-r--r-- | scripts/lib/mic/imager/liveusb.py | 308 | ||||
-rw-r--r-- | scripts/lib/mic/imager/loop.py | 418 | ||||
-rw-r--r-- | scripts/lib/mic/imager/raw.py | 501 |
7 files changed, 3411 insertions, 0 deletions
diff --git a/scripts/lib/mic/imager/__init__.py b/scripts/lib/mic/imager/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/scripts/lib/mic/imager/__init__.py diff --git a/scripts/lib/mic/imager/baseimager.py b/scripts/lib/mic/imager/baseimager.py new file mode 100644 index 0000000000..6efc6c1294 --- /dev/null +++ b/scripts/lib/mic/imager/baseimager.py @@ -0,0 +1,1335 @@ + +#!/usr/bin/python -tt +# +# Copyright (c) 2007 Red Hat Inc. +# Copyright (c) 2009, 2010, 2011 Intel, Inc. +# +# 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 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. + +from __future__ import with_statement +import os, sys +import stat +import tempfile +import shutil +import subprocess +import re +import tarfile +import glob + +import rpm + +from mic import kickstart +from mic import msger +from mic.utils.errors import CreatorError, Abort +from mic.utils import misc, grabber, runner, fs_related as fs + +class BaseImageCreator(object): + """Installs a system to a chroot directory. + + ImageCreator is the simplest creator class available; it will install and + configure a system image according to the supplied kickstart file. + + e.g. + + import mic.imgcreate as imgcreate + ks = imgcreate.read_kickstart("foo.ks") + imgcreate.ImageCreator(ks, "foo").create() + + """ + + def __del__(self): + self.cleanup() + + def __init__(self, createopts = None, pkgmgr = None): + """Initialize an ImageCreator instance. + + ks -- a pykickstart.KickstartParser instance; this instance will be + used to drive the install by e.g. providing the list of packages + to be installed, the system configuration and %post scripts + + name -- a name for the image; used for e.g. image filenames or + filesystem labels + """ + + self.pkgmgr = pkgmgr + + self.__builddir = None + self.__bindmounts = [] + + self.ks = None + self.name = "target" + self.tmpdir = "/var/tmp/mic" + self.cachedir = "/var/tmp/mic/cache" + self.workdir = "/var/tmp/mic/build" + self.destdir = "." + self.installerfw_prefix = "INSTALLERFW_" + self.target_arch = "noarch" + self._local_pkgs_path = None + self.pack_to = None + self.repourl = {} + + # If the kernel is save to the destdir when copy_kernel cmd is called. + self._need_copy_kernel = False + # setup tmpfs tmpdir when enabletmpfs is True + self.enabletmpfs = False + + if createopts: + # Mapping table for variables that have different names. + optmap = {"pkgmgr" : "pkgmgr_name", + "outdir" : "destdir", + "arch" : "target_arch", + "local_pkgs_path" : "_local_pkgs_path", + "copy_kernel" : "_need_copy_kernel", + } + + # update setting from createopts + for key in createopts.keys(): + if key in optmap: + option = optmap[key] + else: + option = key + setattr(self, option, createopts[key]) + + self.destdir = os.path.abspath(os.path.expanduser(self.destdir)) + + if 'release' in createopts and createopts['release']: + self.name = createopts['release'] + '_' + self.name + + if self.pack_to: + if '@NAME@' in self.pack_to: + self.pack_to = self.pack_to.replace('@NAME@', self.name) + (tar, ext) = os.path.splitext(self.pack_to) + if ext in (".gz", ".bz2") and tar.endswith(".tar"): + ext = ".tar" + ext + if ext not in misc.pack_formats: + self.pack_to += ".tar" + + self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"] + + # Output image file names + self.outimage = [] + + # A flag to generate checksum + self._genchecksum = False + + self._alt_initrd_name = None + + self._recording_pkgs = [] + + # available size in root fs, init to 0 + self._root_fs_avail = 0 + + # Name of the disk image file that is created. + self._img_name = None + + self.image_format = None + + # Save qemu emulator file name in order to clean up it finally + self.qemu_emulator = None + + # No ks provided when called by convertor, so skip the dependency check + if self.ks: + # If we have btrfs partition we need to check necessary tools + for part in self.ks.handler.partition.partitions: + if part.fstype and part.fstype == "btrfs": + self._dep_checks.append("mkfs.btrfs") + break + + if self.target_arch and self.target_arch.startswith("arm"): + for dep in self._dep_checks: + if dep == "extlinux": + self._dep_checks.remove(dep) + + if not os.path.exists("/usr/bin/qemu-arm") or \ + not misc.is_statically_linked("/usr/bin/qemu-arm"): + self._dep_checks.append("qemu-arm-static") + + if os.path.exists("/proc/sys/vm/vdso_enabled"): + vdso_fh = open("/proc/sys/vm/vdso_enabled","r") + vdso_value = vdso_fh.read().strip() + vdso_fh.close() + if (int)(vdso_value) == 1: + msger.warning("vdso is enabled on your host, which might " + "cause problems with arm emulations.\n" + "\tYou can disable vdso with following command before " + "starting image build:\n" + "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled") + + # make sure the specified tmpdir and cachedir exist + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + if not os.path.exists(self.cachedir): + os.makedirs(self.cachedir) + + + # + # Properties + # + def __get_instroot(self): + if self.__builddir is None: + raise CreatorError("_instroot is not valid before calling mount()") + return self.__builddir + "/install_root" + _instroot = property(__get_instroot) + """The location of the install root directory. + + This is the directory into which the system is installed. Subclasses may + mount a filesystem image here or copy files to/from here. + + Note, this directory does not exist before ImageCreator.mount() is called. + + Note also, this is a read-only attribute. + + """ + + def __get_outdir(self): + if self.__builddir is None: + raise CreatorError("_outdir is not valid before calling mount()") + return self.__builddir + "/out" + _outdir = property(__get_outdir) + """The staging location for the final image. + + This is where subclasses should stage any files that are part of the final + image. ImageCreator.package() will copy any files found here into the + requested destination directory. + + Note, this directory does not exist before ImageCreator.mount() is called. + + Note also, this is a read-only attribute. + + """ + + + # + # Hooks for subclasses + # + def _mount_instroot(self, base_on = None): + """Mount or prepare the install root directory. + + This is the hook where subclasses may prepare the install root by e.g. + mounting creating and loopback mounting a filesystem image to + _instroot. + + There is no default implementation. + + base_on -- this is the value passed to mount() and can be interpreted + as the subclass wishes; it might e.g. be the location of + a previously created ISO containing a system image. + + """ + pass + + def _unmount_instroot(self): + """Undo anything performed in _mount_instroot(). + + This is the hook where subclasses must undo anything which was done + in _mount_instroot(). For example, if a filesystem image was mounted + onto _instroot, it should be unmounted here. + + There is no default implementation. + + """ + pass + + def _create_bootconfig(self): + """Configure the image so that it's bootable. + + This is the hook where subclasses may prepare the image for booting by + e.g. creating an initramfs and bootloader configuration. + + This hook is called while the install root is still mounted, after the + packages have been installed and the kickstart configuration has been + applied, but before the %post scripts have been executed. + + There is no default implementation. + + """ + pass + + def _stage_final_image(self): + """Stage the final system image in _outdir. + + This is the hook where subclasses should place the image in _outdir + so that package() can copy it to the requested destination directory. + + By default, this moves the install root into _outdir. + + """ + shutil.move(self._instroot, self._outdir + "/" + self.name) + + def get_installed_packages(self): + return self._pkgs_content.keys() + + def _save_recording_pkgs(self, destdir): + """Save the list or content of installed packages to file. + """ + pkgs = self._pkgs_content.keys() + pkgs.sort() # inplace op + + if not os.path.exists(destdir): + os.makedirs(destdir) + + content = None + if 'vcs' in self._recording_pkgs: + vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()] + content = '\n'.join(sorted(vcslst)) + elif 'name' in self._recording_pkgs: + content = '\n'.join(pkgs) + if content: + namefile = os.path.join(destdir, self.name + '.packages') + f = open(namefile, "w") + f.write(content) + f.close() + self.outimage.append(namefile); + + # if 'content', save more details + if 'content' in self._recording_pkgs: + contfile = os.path.join(destdir, self.name + '.files') + f = open(contfile, "w") + + for pkg in pkgs: + content = pkg + '\n' + + pkgcont = self._pkgs_content[pkg] + content += ' ' + content += '\n '.join(pkgcont) + content += '\n' + + content += '\n' + f.write(content) + f.close() + self.outimage.append(contfile) + + if 'license' in self._recording_pkgs: + licensefile = os.path.join(destdir, self.name + '.license') + f = open(licensefile, "w") + + f.write('Summary:\n') + for license in reversed(sorted(self._pkgs_license, key=\ + lambda license: len(self._pkgs_license[license]))): + f.write(" - %s: %s\n" \ + % (license, len(self._pkgs_license[license]))) + + f.write('\nDetails:\n') + for license in reversed(sorted(self._pkgs_license, key=\ + lambda license: len(self._pkgs_license[license]))): + f.write(" - %s:\n" % (license)) + for pkg in sorted(self._pkgs_license[license]): + f.write(" - %s\n" % (pkg)) + f.write('\n') + + f.close() + self.outimage.append(licensefile) + + def _get_required_packages(self): + """Return a list of required packages. + + This is the hook where subclasses may specify a set of packages which + it requires to be installed. + + This returns an empty list by default. + + Note, subclasses should usually chain up to the base class + implementation of this hook. + + """ + return [] + + def _get_excluded_packages(self): + """Return a list of excluded packages. + + This is the hook where subclasses may specify a set of packages which + it requires _not_ to be installed. + + This returns an empty list by default. + + Note, subclasses should usually chain up to the base class + implementation of this hook. + + """ + return [] + + def _get_local_packages(self): + """Return a list of rpm path to be local installed. + + This is the hook where subclasses may specify a set of rpms which + it requires to be installed locally. + + This returns an empty list by default. + + Note, subclasses should usually chain up to the base class + implementation of this hook. + + """ + if self._local_pkgs_path: + if os.path.isdir(self._local_pkgs_path): + return glob.glob( + os.path.join(self._local_pkgs_path, '*.rpm')) + elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm': + return [self._local_pkgs_path] + + return [] + + def _get_fstab(self): + """Return the desired contents of /etc/fstab. + + This is the hook where subclasses may specify the contents of + /etc/fstab by returning a string containing the desired contents. + + A sensible default implementation is provided. + + """ + s = "/dev/root / %s %s 0 0\n" \ + % (self._fstype, + "defaults,noatime" if not self._fsopts else self._fsopts) + s += self._get_fstab_special() + return s + + def _get_fstab_special(self): + s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n" + s += "tmpfs /dev/shm tmpfs defaults 0 0\n" + s += "proc /proc proc defaults 0 0\n" + s += "sysfs /sys sysfs defaults 0 0\n" + return s + + def _set_part_env(self, pnum, prop, value): + """ This is a helper function which generates an environment variable + for a property "prop" with value "value" of a partition number "pnum". + + The naming convention is: + * Variables start with INSTALLERFW_PART + * Then goes the partition number, the order is the same as + specified in the KS file + * Then goes the property name + """ + + if value == None: + value = "" + else: + value = str(value) + + name = self.installerfw_prefix + ("PART%d_" % pnum) + prop + return { name : value } + + def _get_post_scripts_env(self, in_chroot): + """Return an environment dict for %post scripts. + + This is the hook where subclasses may specify some environment + variables for %post scripts by return a dict containing the desired + environment. + + in_chroot -- whether this %post script is to be executed chroot()ed + into _instroot. + """ + + env = {} + pnum = 0 + + for p in kickstart.get_partitions(self.ks): + env.update(self._set_part_env(pnum, "SIZE", p.size)) + env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint)) + env.update(self._set_part_env(pnum, "FSTYPE", p.fstype)) + env.update(self._set_part_env(pnum, "LABEL", p.label)) + env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts)) + env.update(self._set_part_env(pnum, "BOOTFLAG", p.active)) + env.update(self._set_part_env(pnum, "ALIGN", p.align)) + env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type)) + env.update(self._set_part_env(pnum, "DEVNODE", + "/dev/%s%d" % (p.disk, pnum + 1))) + pnum += 1 + + # Count of paritions + env[self.installerfw_prefix + "PART_COUNT"] = str(pnum) + + # Partition table format + ptable_format = self.ks.handler.bootloader.ptable + env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format + + # The kerned boot parameters + kernel_opts = self.ks.handler.bootloader.appendLine + env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts + + # Name of the distribution + env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name + + # Name of the image creation tool + env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic" + + # The real current location of the mounted file-systems + if in_chroot: + mount_prefix = "/" + else: + mount_prefix = self._instroot + env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix + + # These are historical variables which lack the common name prefix + if not in_chroot: + env["INSTALL_ROOT"] = self._instroot + env["IMG_NAME"] = self._name + + return env + + def __get_imgname(self): + return self.name + _name = property(__get_imgname) + """The name of the image file. + + """ + + def _get_kernel_versions(self): + """Return a dict detailing the available kernel types/versions. + + This is the hook where subclasses may override what kernel types and + versions should be available for e.g. creating the booloader + configuration. + + A dict should be returned mapping the available kernel types to a list + of the available versions for those kernels. + + The default implementation uses rpm to iterate over everything + providing 'kernel', finds /boot/vmlinuz-* and returns the version + obtained from the vmlinuz filename. (This can differ from the kernel + RPM's n-v-r in the case of e.g. xen) + + """ + def get_kernel_versions(instroot): + ret = {} + versions = set() + files = glob.glob(instroot + "/boot/vmlinuz-*") + for file in files: + version = os.path.basename(file)[8:] + if version is None: + continue + versions.add(version) + ret["kernel"] = list(versions) + return ret + + def get_version(header): + version = None + for f in header['filenames']: + if f.startswith('/boot/vmlinuz-'): + version = f[14:] + return version + + if self.ks is None: + return get_kernel_versions(self._instroot) + + ts = rpm.TransactionSet(self._instroot) + + ret = {} + for header in ts.dbMatch('provides', 'kernel'): + version = get_version(header) + if version is None: + continue + + name = header['name'] + if not name in ret: + ret[name] = [version] + elif not version in ret[name]: + ret[name].append(version) + + return ret + + + # + # Helpers for subclasses + # + def _do_bindmounts(self): + """Mount various system directories onto _instroot. + + This method is called by mount(), but may also be used by subclasses + in order to re-mount the bindmounts after modifying the underlying + filesystem. + + """ + for b in self.__bindmounts: + b.mount() + + def _undo_bindmounts(self): + """Unmount the bind-mounted system directories from _instroot. + + This method is usually only called by unmount(), but may also be used + by subclasses in order to gain access to the filesystem obscured by + the bindmounts - e.g. in order to create device nodes on the image + filesystem. + + """ + self.__bindmounts.reverse() + for b in self.__bindmounts: + b.unmount() + + def _chroot(self): + """Chroot into the install root. + + This method may be used by subclasses when executing programs inside + the install root e.g. + + subprocess.call(["/bin/ls"], preexec_fn = self.chroot) + + """ + os.chroot(self._instroot) + os.chdir("/") + + def _mkdtemp(self, prefix = "tmp-"): + """Create a temporary directory. + + This method may be used by subclasses to create a temporary directory + for use in building the final image - e.g. a subclass might create + a temporary directory in order to bundle a set of files into a package. + + The subclass may delete this directory if it wishes, but it will be + automatically deleted by cleanup(). + + The absolute path to the temporary directory is returned. + + Note, this method should only be called after mount() has been called. + + prefix -- a prefix which should be used when creating the directory; + defaults to "tmp-". + + """ + self.__ensure_builddir() + return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix) + + def _mkstemp(self, prefix = "tmp-"): + """Create a temporary file. + + This method may be used by subclasses to create a temporary file + for use in building the final image - e.g. a subclass might need + a temporary location to unpack a compressed file. + + The subclass may delete this file if it wishes, but it will be + automatically deleted by cleanup(). + + A tuple containing a file descriptor (returned from os.open() and the + absolute path to the temporary directory is returned. + + Note, this method should only be called after mount() has been called. + + prefix -- a prefix which should be used when creating the file; + defaults to "tmp-". + + """ + self.__ensure_builddir() + return tempfile.mkstemp(dir = self.__builddir, prefix = prefix) + + def _mktemp(self, prefix = "tmp-"): + """Create a temporary file. + + This method simply calls _mkstemp() and closes the returned file + descriptor. + + The absolute path to the temporary file is returned. + + Note, this method should only be called after mount() has been called. + + prefix -- a prefix which should be used when creating the file; + defaults to "tmp-". + + """ + + (f, path) = self._mkstemp(prefix) + os.close(f) + return path + + + # + # Actual implementation + # + def __ensure_builddir(self): + if not self.__builddir is None: + return + + try: + self.workdir = os.path.join(self.tmpdir, "build") + if not os.path.exists(self.workdir): + os.makedirs(self.workdir) + self.__builddir = tempfile.mkdtemp(dir = self.workdir, + prefix = "imgcreate-") + except OSError, (err, msg): + raise CreatorError("Failed create build directory in %s: %s" % + (self.tmpdir, msg)) + + def get_cachedir(self, cachedir = None): + if self.cachedir: + return self.cachedir + + self.__ensure_builddir() + if cachedir: + self.cachedir = cachedir + else: + self.cachedir = self.__builddir + "/mic-cache" + fs.makedirs(self.cachedir) + return self.cachedir + + def __sanity_check(self): + """Ensure that the config we've been given is sane.""" + if not (kickstart.get_packages(self.ks) or + kickstart.get_groups(self.ks)): + raise CreatorError("No packages or groups specified") + + kickstart.convert_method_to_repo(self.ks) + + if not kickstart.get_repos(self.ks): + raise CreatorError("No repositories specified") + + def __write_fstab(self): + fstab_contents = self._get_fstab() + if fstab_contents: + fstab = open(self._instroot + "/etc/fstab", "w") + fstab.write(fstab_contents) + fstab.close() + + def __create_minimal_dev(self): + """Create a minimal /dev so that we don't corrupt the host /dev""" + origumask = os.umask(0000) + devices = (('null', 1, 3, 0666), + ('urandom',1, 9, 0666), + ('random', 1, 8, 0666), + ('full', 1, 7, 0666), + ('ptmx', 5, 2, 0666), + ('tty', 5, 0, 0666), + ('zero', 1, 5, 0666)) + + links = (("/proc/self/fd", "/dev/fd"), + ("/proc/self/fd/0", "/dev/stdin"), + ("/proc/self/fd/1", "/dev/stdout"), + ("/proc/self/fd/2", "/dev/stderr")) + + for (node, major, minor, perm) in devices: + if not os.path.exists(self._instroot + "/dev/" + node): + os.mknod(self._instroot + "/dev/" + node, + perm | stat.S_IFCHR, + os.makedev(major,minor)) + + for (src, dest) in links: + if not os.path.exists(self._instroot + dest): + os.symlink(src, self._instroot + dest) + + os.umask(origumask) + + def __setup_tmpdir(self): + if not self.enabletmpfs: + return + + runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir) + + def __clean_tmpdir(self): + if not self.enabletmpfs: + return + + runner.show('umount -l %s' % self.workdir) + + def mount(self, base_on = None, cachedir = None): + """Setup the target filesystem in preparation for an install. + + This function sets up the filesystem which the ImageCreator will + install into and configure. The ImageCreator class merely creates an + install root directory, bind mounts some system directories (e.g. /dev) + and writes out /etc/fstab. Other subclasses may also e.g. create a + sparse file, format it and loopback mount it to the install root. + + base_on -- a previous install on which to base this install; defaults + to None, causing a new image to be created + + cachedir -- a directory in which to store the Yum cache; defaults to + None, causing a new cache to be created; by setting this + to another directory, the same cache can be reused across + multiple installs. + + """ + self.__setup_tmpdir() + self.__ensure_builddir() + + # prevent popup dialog in Ubuntu(s) + misc.hide_loopdev_presentation() + + fs.makedirs(self._instroot) + fs.makedirs(self._outdir) + + self._mount_instroot(base_on) + + for d in ("/dev/pts", + "/etc", + "/boot", + "/var/log", + "/sys", + "/proc", + "/usr/bin"): + fs.makedirs(self._instroot + d) + + if self.target_arch and self.target_arch.startswith("arm"): + self.qemu_emulator = misc.setup_qemu_emulator(self._instroot, + self.target_arch) + + + self.get_cachedir(cachedir) + + # bind mount system directories into _instroot + for (f, dest) in [("/sys", None), + ("/proc", None), + ("/proc/sys/fs/binfmt_misc", None), + ("/dev/pts", None)]: + self.__bindmounts.append( + fs.BindChrootMount( + f, self._instroot, dest)) + + self._do_bindmounts() + + self.__create_minimal_dev() + + if os.path.exists(self._instroot + "/etc/mtab"): + os.unlink(self._instroot + "/etc/mtab") + os.symlink("../proc/mounts", self._instroot + "/etc/mtab") + + self.__write_fstab() + + # get size of available space in 'instroot' fs + self._root_fs_avail = misc.get_filesystem_avail(self._instroot) + + def unmount(self): + """Unmounts the target filesystem. + + The ImageCreator class detaches the system from the install root, but + other subclasses may also detach the loopback mounted filesystem image + from the install root. + + """ + try: + mtab = self._instroot + "/etc/mtab" + if not os.path.islink(mtab): + os.unlink(self._instroot + "/etc/mtab") + + if self.qemu_emulator: + os.unlink(self._instroot + self.qemu_emulator) + except OSError: + pass + + self._undo_bindmounts() + + """ Clean up yum garbage """ + try: + instroot_pdir = os.path.dirname(self._instroot + self._instroot) + if os.path.exists(instroot_pdir): + shutil.rmtree(instroot_pdir, ignore_errors = True) + yumlibdir = self._instroot + "/var/lib/yum" + if os.path.exists(yumlibdir): + shutil.rmtree(yumlibdir, ignore_errors = True) + except OSError: + pass + + self._unmount_instroot() + + # reset settings of popup dialog in Ubuntu(s) + misc.unhide_loopdev_presentation() + + + def cleanup(self): + """Unmounts the target filesystem and deletes temporary files. + + This method calls unmount() and then deletes any temporary files and + directories that were created on the host system while building the + image. + + Note, make sure to call this method once finished with the creator + instance in order to ensure no stale files are left on the host e.g.: + + creator = ImageCreator(ks, name) + try: + creator.create() + finally: + creator.cleanup() + + """ + if not self.__builddir: + return + + self.unmount() + + shutil.rmtree(self.__builddir, ignore_errors = True) + self.__builddir = None + + self.__clean_tmpdir() + + def __is_excluded_pkg(self, pkg): + if pkg in self._excluded_pkgs: + self._excluded_pkgs.remove(pkg) + return True + + for xpkg in self._excluded_pkgs: + if xpkg.endswith('*'): + if pkg.startswith(xpkg[:-1]): + return True + elif xpkg.startswith('*'): + if pkg.endswith(xpkg[1:]): + return True + + return None + + def __select_packages(self, pkg_manager): + skipped_pkgs = [] + for pkg in self._required_pkgs: + e = pkg_manager.selectPackage(pkg) + if e: + if kickstart.ignore_missing(self.ks): + skipped_pkgs.append(pkg) + elif self.__is_excluded_pkg(pkg): + skipped_pkgs.append(pkg) + else: + raise CreatorError("Failed to find package '%s' : %s" % + (pkg, e)) + + for pkg in skipped_pkgs: + msger.warning("Skipping missing package '%s'" % (pkg,)) + + def __select_groups(self, pkg_manager): + skipped_groups = [] + for group in self._required_groups: + e = pkg_manager.selectGroup(group.name, group.include) + if e: + if kickstart.ignore_missing(self.ks): + skipped_groups.append(group) + else: + raise CreatorError("Failed to find group '%s' : %s" % + (group.name, e)) + + for group in skipped_groups: + msger.warning("Skipping missing group '%s'" % (group.name,)) + + def __deselect_packages(self, pkg_manager): + for pkg in self._excluded_pkgs: + pkg_manager.deselectPackage(pkg) + + def __localinst_packages(self, pkg_manager): + for rpm_path in self._get_local_packages(): + pkg_manager.installLocal(rpm_path) + + def __preinstall_packages(self, pkg_manager): + if not self.ks: + return + + self._preinstall_pkgs = kickstart.get_pre_packages(self.ks) + for pkg in self._preinstall_pkgs: + pkg_manager.preInstall(pkg) + + def __attachment_packages(self, pkg_manager): + if not self.ks: + return + + self._attachment = [] + for item in kickstart.get_attachment(self.ks): + if item.startswith('/'): + fpaths = os.path.join(self._instroot, item.lstrip('/')) + for fpath in glob.glob(fpaths): + self._attachment.append(fpath) + continue + + filelist = pkg_manager.getFilelist(item) + if filelist: + # found rpm in rootfs + for pfile in pkg_manager.getFilelist(item): + fpath = os.path.join(self._instroot, pfile.lstrip('/')) + self._attachment.append(fpath) + continue + + # try to retrieve rpm file + (url, proxies) = pkg_manager.package_url(item) + if not url: + msger.warning("Can't get url from repo for %s" % item) + continue + fpath = os.path.join(self.cachedir, os.path.basename(url)) + if not os.path.exists(fpath): + # download pkgs + try: + fpath = grabber.myurlgrab(url, fpath, proxies, None) + except CreatorError: + raise + + tmpdir = self._mkdtemp() + misc.extract_rpm(fpath, tmpdir) + for (root, dirs, files) in os.walk(tmpdir): + for fname in files: + fpath = os.path.join(root, fname) + self._attachment.append(fpath) + + def install(self, repo_urls=None): + """Install packages into the install root. + + This function installs the packages listed in the supplied kickstart + into the install root. By default, the packages are installed from the + repository URLs specified in the kickstart. + + repo_urls -- a dict which maps a repository name to a repository URL; + if supplied, this causes any repository URLs specified in + the kickstart to be overridden. + + """ + + # initialize pkg list to install + if self.ks: + self.__sanity_check() + + self._required_pkgs = \ + kickstart.get_packages(self.ks, self._get_required_packages()) + self._excluded_pkgs = \ + kickstart.get_excluded(self.ks, self._get_excluded_packages()) + self._required_groups = kickstart.get_groups(self.ks) + else: + self._required_pkgs = None + self._excluded_pkgs = None + self._required_groups = None + + pkg_manager = self.get_pkg_manager() + pkg_manager.setup() + + if hasattr(self, 'install_pkgs') and self.install_pkgs: + if 'debuginfo' in self.install_pkgs: + pkg_manager.install_debuginfo = True + + for repo in kickstart.get_repos(self.ks, repo_urls): + (name, baseurl, mirrorlist, inc, exc, + proxy, proxy_username, proxy_password, debuginfo, + source, gpgkey, disable, ssl_verify, nocache, + cost, priority) = repo + + yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy, + proxy_username, proxy_password, inc, exc, ssl_verify, + nocache, cost, priority) + + if kickstart.exclude_docs(self.ks): + rpm.addMacro("_excludedocs", "1") + rpm.addMacro("_dbpath", "/var/lib/rpm") + rpm.addMacro("__file_context_path", "%{nil}") + if kickstart.inst_langs(self.ks) != None: + rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks)) + + try: + self.__preinstall_packages(pkg_manager) + self.__select_packages(pkg_manager) + self.__select_groups(pkg_manager) + self.__deselect_packages(pkg_manager) + self.__localinst_packages(pkg_manager) + + BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M + checksize = self._root_fs_avail + if checksize: + checksize -= BOOT_SAFEGUARD + if self.target_arch: + pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH) + pkg_manager.runInstall(checksize) + except CreatorError, e: + raise + except KeyboardInterrupt: + raise + else: + self._pkgs_content = pkg_manager.getAllContent() + self._pkgs_license = pkg_manager.getPkgsLicense() + self._pkgs_vcsinfo = pkg_manager.getVcsInfo() + self.__attachment_packages(pkg_manager) + finally: + pkg_manager.close() + + # hook post install + self.postinstall() + + # do some clean up to avoid lvm info leakage. this sucks. + for subdir in ("cache", "backup", "archive"): + lvmdir = self._instroot + "/etc/lvm/" + subdir + try: + for f in os.listdir(lvmdir): + os.unlink(lvmdir + "/" + f) + except: + pass + + def postinstall(self): + self.copy_attachment() + + def __run_post_scripts(self): + msger.info("Running scripts ...") + if os.path.exists(self._instroot + "/tmp"): + shutil.rmtree(self._instroot + "/tmp") + os.mkdir (self._instroot + "/tmp", 0755) + for s in kickstart.get_post_scripts(self.ks): + (fd, path) = tempfile.mkstemp(prefix = "ks-script-", + dir = self._instroot + "/tmp") + + s.script = s.script.replace("\r", "") + os.write(fd, s.script) + os.close(fd) + os.chmod(path, 0700) + + env = self._get_post_scripts_env(s.inChroot) + + if not s.inChroot: + preexec = None + script = path + else: + preexec = self._chroot + script = "/tmp/" + os.path.basename(path) + + try: + try: + subprocess.call([s.interp, script], + preexec_fn = preexec, + env = env, + stdout = sys.stdout, + stderr = sys.stderr) + except OSError, (err, msg): + raise CreatorError("Failed to execute %%post script " + "with '%s' : %s" % (s.interp, msg)) + finally: + os.unlink(path) + + def __save_repo_keys(self, repodata): + if not repodata: + return None + + gpgkeydir = "/etc/pki/rpm-gpg" + fs.makedirs(self._instroot + gpgkeydir) + for repo in repodata: + if repo["repokey"]: + repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"] + shutil.copy(repo["repokey"], self._instroot + repokey) + + def configure(self, repodata = None): + """Configure the system image according to the kickstart. + + This method applies the (e.g. keyboard or network) configuration + specified in the kickstart and executes the kickstart %post scripts. + + If necessary, it also prepares the image to be bootable by e.g. + creating an initrd and bootloader configuration. + + """ + ksh = self.ks.handler + + msger.info('Applying configurations ...') + try: + kickstart.LanguageConfig(self._instroot).apply(ksh.lang) + kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard) + kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone) + #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig) + kickstart.FirewallConfig(self._instroot).apply(ksh.firewall) + kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw) + kickstart.UserConfig(self._instroot).apply(ksh.user) + kickstart.ServicesConfig(self._instroot).apply(ksh.services) + kickstart.XConfig(self._instroot).apply(ksh.xconfig) + kickstart.NetworkConfig(self._instroot).apply(ksh.network) + kickstart.RPMMacroConfig(self._instroot).apply(self.ks) + kickstart.DesktopConfig(self._instroot).apply(ksh.desktop) + self.__save_repo_keys(repodata) + kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl) + except: + msger.warning("Failed to apply configuration to image") + raise + + self._create_bootconfig() + self.__run_post_scripts() + + def launch_shell(self, launch): + """Launch a shell in the install root. + + This method is launches a bash shell chroot()ed in the install root; + this can be useful for debugging. + + """ + if launch: + msger.info("Launching shell. Exit to continue.") + subprocess.call(["/bin/bash"], preexec_fn = self._chroot) + + def do_genchecksum(self, image_name): + if not self._genchecksum: + return + + md5sum = misc.get_md5sum(image_name) + with open(image_name + ".md5sum", "w") as f: + f.write("%s %s" % (md5sum, os.path.basename(image_name))) + self.outimage.append(image_name+".md5sum") + + def package(self, destdir = "."): + """Prepares the created image for final delivery. + + In its simplest form, this method merely copies the install root to the + supplied destination directory; other subclasses may choose to package + the image by e.g. creating a bootable ISO containing the image and + bootloader configuration. + + destdir -- the directory into which the final image should be moved; + this defaults to the current directory. + + """ + self._stage_final_image() + + if not os.path.exists(destdir): + fs.makedirs(destdir) + + if self._recording_pkgs: + self._save_recording_pkgs(destdir) + + # For image formats with two or multiple image files, it will be + # better to put them under a directory + if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"): + destdir = os.path.join(destdir, "%s-%s" \ + % (self.name, self.image_format)) + msger.debug("creating destination dir: %s" % destdir) + fs.makedirs(destdir) + + # Ensure all data is flushed to _outdir + runner.quiet('sync') + + misc.check_space_pre_cp(self._outdir, destdir) + for f in os.listdir(self._outdir): + shutil.move(os.path.join(self._outdir, f), + os.path.join(destdir, f)) + self.outimage.append(os.path.join(destdir, f)) + self.do_genchecksum(os.path.join(destdir, f)) + + def print_outimage_info(self): + msg = "The new image can be found here:\n" + self.outimage.sort() + for file in self.outimage: + msg += ' %s\n' % os.path.abspath(file) + + msger.info(msg) + + def check_depend_tools(self): + for tool in self._dep_checks: + fs.find_binary_path(tool) + + def package_output(self, image_format, destdir = ".", package="none"): + if not package or package == "none": + return + + destdir = os.path.abspath(os.path.expanduser(destdir)) + (pkg, comp) = os.path.splitext(package) + if comp: + comp=comp.lstrip(".") + + if pkg == "tar": + if comp: + dst = "%s/%s-%s.tar.%s" %\ + (destdir, self.name, image_format, comp) + else: + dst = "%s/%s-%s.tar" %\ + (destdir, self.name, image_format) + + msger.info("creating %s" % dst) + tar = tarfile.open(dst, "w:" + comp) + + for file in self.outimage: + msger.info("adding %s to %s" % (file, dst)) + tar.add(file, + arcname=os.path.join("%s-%s" \ + % (self.name, image_format), + os.path.basename(file))) + if os.path.isdir(file): + shutil.rmtree(file, ignore_errors = True) + else: + os.remove(file) + + tar.close() + + '''All the file in outimage has been packaged into tar.* file''' + self.outimage = [dst] + + def release_output(self, config, destdir, release): + """ Create release directory and files + """ + + def _rpath(fn): + """ release path """ + return os.path.join(destdir, fn) + + outimages = self.outimage + + # new ks + new_kspath = _rpath(self.name+'.ks') + with open(config) as fr: + with open(new_kspath, "w") as wf: + # When building a release we want to make sure the .ks + # file generates the same build even when --release not used. + wf.write(fr.read().replace("@BUILD_ID@", release)) + outimages.append(new_kspath) + + # save log file, logfile is only available in creator attrs + if hasattr(self, 'logfile') and not self.logfile: + log_path = _rpath(self.name + ".log") + # touch the log file, else outimages will filter it out + with open(log_path, 'w') as wf: + wf.write('') + msger.set_logfile(log_path) + outimages.append(_rpath(self.name + ".log")) + + # rename iso and usbimg + for f in os.listdir(destdir): + if f.endswith(".iso"): + newf = f[:-4] + '.img' + elif f.endswith(".usbimg"): + newf = f[:-7] + '.img' + else: + continue + os.rename(_rpath(f), _rpath(newf)) + outimages.append(_rpath(newf)) + + # generate MD5SUMS + with open(_rpath("MD5SUMS"), "w") as wf: + for f in os.listdir(destdir): + if f == "MD5SUMS": + continue + + if os.path.isdir(os.path.join(destdir, f)): + continue + + md5sum = misc.get_md5sum(_rpath(f)) + # There needs to be two spaces between the sum and + # filepath to match the syntax with md5sum. + # This way also md5sum -c MD5SUMS can be used by users + wf.write("%s *%s\n" % (md5sum, f)) + + outimages.append("%s/MD5SUMS" % destdir) + + # Filter out the nonexist file + for fp in outimages[:]: + if not os.path.exists("%s" % fp): + outimages.remove(fp) + + def copy_kernel(self): + """ Copy kernel files to the outimage directory. + NOTE: This needs to be called before unmounting the instroot. + """ + + if not self._need_copy_kernel: + return + + if not os.path.exists(self.destdir): + os.makedirs(self.destdir) + + for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot): + kernelfilename = "%s/%s-%s" % (self.destdir, + self.name, + os.path.basename(kernel)) + msger.info('copy kernel file %s as %s' % (os.path.basename(kernel), + kernelfilename)) + shutil.copy(kernel, kernelfilename) + self.outimage.append(kernelfilename) + + def copy_attachment(self): + """ Subclass implement it to handle attachment files + NOTE: This needs to be called before unmounting the instroot. + """ + pass + + def get_pkg_manager(self): + return self.pkgmgr(target_arch = self.target_arch, + instroot = self._instroot, + cachedir = self.cachedir) diff --git a/scripts/lib/mic/imager/fs.py b/scripts/lib/mic/imager/fs.py new file mode 100644 index 0000000000..d53b29cb47 --- /dev/null +++ b/scripts/lib/mic/imager/fs.py @@ -0,0 +1,99 @@ +#!/usr/bin/python -tt +# +# Copyright (c) 2011 Intel, Inc. +# +# 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 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 + +from mic import msger +from mic.utils import runner, misc +from mic.utils.errors import CreatorError +from mic.utils.fs_related import find_binary_path +from mic.imager.baseimager import BaseImageCreator + +class FsImageCreator(BaseImageCreator): + def __init__(self, cfgmgr = None, pkgmgr = None): + self.zips = { + "tar.bz2" : "" + } + BaseImageCreator.__init__(self, cfgmgr, pkgmgr) + self._fstype = None + self._fsopts = None + self._include_src = False + + def package(self, destdir = "."): + + ignores = ["/dev/fd", + "/dev/stdin", + "/dev/stdout", + "/dev/stderr", + "/etc/mtab"] + + if not os.path.exists(destdir): + os.makedirs(destdir) + + if self._recording_pkgs: + self._save_recording_pkgs(destdir) + + if not self.pack_to: + fsdir = os.path.join(destdir, self.name) + + misc.check_space_pre_cp(self._instroot, destdir) + msger.info("Copying %s to %s ..." % (self._instroot, fsdir)) + runner.show(['cp', "-af", self._instroot, fsdir]) + + for exclude in ignores: + if os.path.exists(fsdir + exclude): + os.unlink(fsdir + exclude) + + self.outimage.append(fsdir) + + else: + (tar, comp) = os.path.splitext(self.pack_to) + try: + tarcreat = {'.tar': '-cf', + '.gz': '-czf', + '.bz2': '-cjf', + '.tgz': '-czf', + '.tbz': '-cjf'}[comp] + except KeyError: + raise CreatorError("Unsupported comression for this image type:" + " '%s', try '.tar', '.tar.gz', etc" % comp) + + dst = os.path.join(destdir, self.pack_to) + msger.info("Pack rootfs to %s. Please wait..." % dst) + + tar = find_binary_path('tar') + tar_cmdline = [tar, "--numeric-owner", + "--preserve-permissions", + "--preserve-order", + "--one-file-system", + "--directory", + self._instroot] + for ignore_entry in ignores: + if ignore_entry.startswith('/'): + ignore_entry = ignore_entry[1:] + + tar_cmdline.append("--exclude=%s" % (ignore_entry)) + + tar_cmdline.extend([tarcreat, dst, "."]) + + rc = runner.show(tar_cmdline) + if rc: + raise CreatorError("Failed compress image with tar.bz2. " + "Cmdline: %s" % (" ".join(tar_cmdline))) + + self.outimage.append(dst) + diff --git a/scripts/lib/mic/imager/livecd.py b/scripts/lib/mic/imager/livecd.py new file mode 100644 index 0000000000..a992ee0706 --- /dev/null +++ b/scripts/lib/mic/imager/livecd.py @@ -0,0 +1,750 @@ +#!/usr/bin/python -tt +# +# Copyright (c) 2011 Intel, Inc. +# +# 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 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, sys +import glob +import shutil + +from mic import kickstart, msger +from mic.utils import fs_related, rpmmisc, runner, misc +from mic.utils.errors import CreatorError +from mic.imager.loop import LoopImageCreator + + +class LiveImageCreatorBase(LoopImageCreator): + """A base class for LiveCD image creators. + + This class serves as a base class for the architecture-specific LiveCD + image creator subclass, LiveImageCreator. + + LiveImageCreator creates a bootable ISO containing the system image, + bootloader, bootloader configuration, kernel and initramfs. + """ + + def __init__(self, creatoropts = None, pkgmgr = None): + """Initialise a LiveImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__(). + """ + LoopImageCreator.__init__(self, creatoropts, pkgmgr) + + #Controls whether to use squashfs to compress the image. + self.skip_compression = False + + #Controls whether an image minimizing snapshot should be created. + # + #This snapshot can be used when copying the system image from the ISO in + #order to minimize the amount of data that needs to be copied; simply, + #it makes it possible to create a version of the image's filesystem with + #no spare space. + self.skip_minimize = False + + #A flag which indicates i act as a convertor default false + self.actasconvertor = False + + #The bootloader timeout from kickstart. + if self.ks: + self._timeout = kickstart.get_timeout(self.ks, 10) + else: + self._timeout = 10 + + #The default kernel type from kickstart. + if self.ks: + self._default_kernel = kickstart.get_default_kernel(self.ks, + "kernel") + else: + self._default_kernel = None + + if self.ks: + parts = kickstart.get_partitions(self.ks) + if len(parts) > 1: + raise CreatorError("Can't support multi partitions in ks file " + "for this image type") + # FIXME: rename rootfs img to self.name, + # else can't find files when create iso + self._instloops[0]['name'] = self.name + ".img" + + self.__isodir = None + + self.__modules = ["=ata", + "sym53c8xx", + "aic7xxx", + "=usb", + "=firewire", + "=mmc", + "=pcmcia", + "mptsas"] + if self.ks: + self.__modules.extend(kickstart.get_modules(self.ks)) + + self._dep_checks.extend(["isohybrid", + "unsquashfs", + "mksquashfs", + "dd", + "genisoimage"]) + + # + # Hooks for subclasses + # + def _configure_bootloader(self, isodir): + """Create the architecture specific booloader configuration. + + This is the hook where subclasses must create the booloader + configuration in order to allow a bootable ISO to be built. + + isodir -- the directory where the contents of the ISO are to + be staged + """ + raise CreatorError("Bootloader configuration is arch-specific, " + "but not implemented for this arch!") + def _get_menu_options(self): + """Return a menu options string for syslinux configuration. + """ + if self.ks is None: + return "liveinst autoinst" + r = kickstart.get_menu_args(self.ks) + return r + + def _get_kernel_options(self): + """Return a kernel options string for bootloader configuration. + + This is the hook where subclasses may specify a set of kernel + options which should be included in the images bootloader + configuration. + + A sensible default implementation is provided. + """ + + if self.ks is None: + r = "ro rd.live.image" + else: + r = kickstart.get_kernel_args(self.ks) + + return r + + def _get_mkisofs_options(self, isodir): + """Return the architecture specific mkisosfs options. + + This is the hook where subclasses may specify additional arguments + to mkisofs, e.g. to enable a bootable ISO to be built. + + By default, an empty list is returned. + """ + return [] + + # + # Helpers for subclasses + # + def _has_checkisomd5(self): + """Check whether checkisomd5 is available in the install root.""" + def _exists(path): + return os.path.exists(self._instroot + path) + + if _exists("/usr/bin/checkisomd5") and os.path.exists("/usr/bin/implantisomd5"): + return True + + return False + + def __restore_file(self,path): + try: + os.unlink(path) + except: + pass + if os.path.exists(path + '.rpmnew'): + os.rename(path + '.rpmnew', path) + + def _mount_instroot(self, base_on = None): + LoopImageCreator._mount_instroot(self, base_on) + self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd") + self.__write_dracut_conf(self._instroot + "/etc/dracut.conf.d/02livecd.conf") + + def _unmount_instroot(self): + self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd") + self.__restore_file(self._instroot + "/etc/dracut.conf.d/02livecd.conf") + LoopImageCreator._unmount_instroot(self) + + def __ensure_isodir(self): + if self.__isodir is None: + self.__isodir = self._mkdtemp("iso-") + return self.__isodir + + def _get_isodir(self): + return self.__ensure_isodir() + + def _set_isodir(self, isodir = None): + self.__isodir = isodir + + def _create_bootconfig(self): + """Configure the image so that it's bootable.""" + self._configure_bootloader(self.__ensure_isodir()) + + def _get_post_scripts_env(self, in_chroot): + env = LoopImageCreator._get_post_scripts_env(self, in_chroot) + + if not in_chroot: + env["LIVE_ROOT"] = self.__ensure_isodir() + + return env + def __write_dracut_conf(self, path): + if not os.path.exists(os.path.dirname(path)): + fs_related.makedirs(os.path.dirname(path)) + f = open(path, "a") + f.write('add_dracutmodules+=" dmsquash-live pollcdrom "') + f.close() + + def __write_initrd_conf(self, path): + content = "" + if not os.path.exists(os.path.dirname(path)): + fs_related.makedirs(os.path.dirname(path)) + f = open(path, "w") + + content += 'LIVEOS="yes"\n' + content += 'PROBE="no"\n' + content += 'MODULES+="squashfs ext3 ext2 vfat msdos "\n' + content += 'MODULES+="sr_mod sd_mod ide-cd cdrom "\n' + + for module in self.__modules: + if module == "=usb": + content += 'MODULES+="ehci_hcd uhci_hcd ohci_hcd "\n' + content += 'MODULES+="usb_storage usbhid "\n' + elif module == "=firewire": + content += 'MODULES+="firewire-sbp2 firewire-ohci "\n' + content += 'MODULES+="sbp2 ohci1394 ieee1394 "\n' + elif module == "=mmc": + content += 'MODULES+="mmc_block sdhci sdhci-pci "\n' + elif module == "=pcmcia": + content += 'MODULES+="pata_pcmcia "\n' + else: + content += 'MODULES+="' + module + ' "\n' + f.write(content) + f.close() + + def __create_iso(self, isodir): + iso = self._outdir + "/" + self.name + ".iso" + genisoimage = fs_related.find_binary_path("genisoimage") + args = [genisoimage, + "-J", "-r", + "-hide-rr-moved", "-hide-joliet-trans-tbl", + "-V", self.fslabel, + "-o", iso] + + args.extend(self._get_mkisofs_options(isodir)) + + args.append(isodir) + + if runner.show(args) != 0: + raise CreatorError("ISO creation failed!") + + """ It should be ok still even if you haven't isohybrid """ + isohybrid = None + try: + isohybrid = fs_related.find_binary_path("isohybrid") + except: + pass + + if isohybrid: + args = [isohybrid, "-partok", iso ] + if runner.show(args) != 0: + raise CreatorError("Hybrid ISO creation failed!") + + self.__implant_md5sum(iso) + + def __implant_md5sum(self, iso): + """Implant an isomd5sum.""" + if os.path.exists("/usr/bin/implantisomd5"): + implantisomd5 = "/usr/bin/implantisomd5" + else: + msger.warning("isomd5sum not installed; not setting up mediacheck") + implantisomd5 = "" + return + + runner.show([implantisomd5, iso]) + + def _stage_final_image(self): + try: + fs_related.makedirs(self.__ensure_isodir() + "/LiveOS") + + minimal_size = self._resparse() + + if not self.skip_minimize: + fs_related.create_image_minimizer(self.__isodir + \ + "/LiveOS/osmin.img", + self._image, + minimal_size) + + if self.skip_compression: + shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img") + else: + fs_related.makedirs(os.path.join( + os.path.dirname(self._image), + "LiveOS")) + shutil.move(self._image, + os.path.join(os.path.dirname(self._image), + "LiveOS", "ext3fs.img")) + fs_related.mksquashfs(os.path.dirname(self._image), + self.__isodir + "/LiveOS/squashfs.img") + + self.__create_iso(self.__isodir) + + if self.pack_to: + isoimg = os.path.join(self._outdir, self.name + ".iso") + packimg = os.path.join(self._outdir, self.pack_to) + misc.packing(packimg, isoimg) + os.unlink(isoimg) + + finally: + shutil.rmtree(self.__isodir, ignore_errors = True) + self.__isodir = None + +class x86LiveImageCreator(LiveImageCreatorBase): + """ImageCreator for x86 machines""" + def _get_mkisofs_options(self, isodir): + return [ "-b", "isolinux/isolinux.bin", + "-c", "isolinux/boot.cat", + "-no-emul-boot", "-boot-info-table", + "-boot-load-size", "4" ] + + def _get_required_packages(self): + return ["syslinux", "syslinux-extlinux"] + \ + LiveImageCreatorBase._get_required_packages(self) + + def _get_isolinux_stanzas(self, isodir): + return "" + + def __find_syslinux_menu(self): + for menu in ["vesamenu.c32", "menu.c32"]: + if os.path.isfile(self._instroot + "/usr/share/syslinux/" + menu): + return menu + + raise CreatorError("syslinux not installed : " + "no suitable /usr/share/syslinux/*menu.c32 found") + + def __find_syslinux_mboot(self): + # + # We only need the mboot module if we have any xen hypervisors + # + if not glob.glob(self._instroot + "/boot/xen.gz*"): + return None + + return "mboot.c32" + + def __copy_syslinux_files(self, isodir, menu, mboot = None): + files = ["isolinux.bin", menu] + if mboot: + files += [mboot] + + for f in files: + path = self._instroot + "/usr/share/syslinux/" + f + + if not os.path.isfile(path): + raise CreatorError("syslinux not installed : " + "%s not found" % path) + + shutil.copy(path, isodir + "/isolinux/") + + def __copy_syslinux_background(self, isodest): + background_path = self._instroot + \ + "/usr/share/branding/default/syslinux/syslinux-vesa-splash.jpg" + + if not os.path.exists(background_path): + return False + + shutil.copyfile(background_path, isodest) + + return True + + def __copy_kernel_and_initramfs(self, isodir, version, index): + bootdir = self._instroot + "/boot" + isDracut = False + + if self._alt_initrd_name: + src_initrd_path = os.path.join(bootdir, self._alt_initrd_name) + else: + if os.path.exists(bootdir + "/initramfs-" + version + ".img"): + src_initrd_path = os.path.join(bootdir, "initramfs-" +version+ ".img") + isDracut = True + else: + src_initrd_path = os.path.join(bootdir, "initrd-" +version+ ".img") + + try: + msger.debug("copy %s to %s" % (bootdir + "/vmlinuz-" + version, isodir + "/isolinux/vmlinuz" + index)) + shutil.copyfile(bootdir + "/vmlinuz-" + version, + isodir + "/isolinux/vmlinuz" + index) + + msger.debug("copy %s to %s" % (src_initrd_path, isodir + "/isolinux/initrd" + index + ".img")) + shutil.copyfile(src_initrd_path, + isodir + "/isolinux/initrd" + index + ".img") + except: + raise CreatorError("Unable to copy valid kernels or initrds, " + "please check the repo.") + + is_xen = False + if os.path.exists(bootdir + "/xen.gz-" + version[:-3]): + shutil.copyfile(bootdir + "/xen.gz-" + version[:-3], + isodir + "/isolinux/xen" + index + ".gz") + is_xen = True + + return (is_xen,isDracut) + + def __is_default_kernel(self, kernel, kernels): + if len(kernels) == 1: + return True + + if kernel == self._default_kernel: + return True + + if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel: + return True + + return False + + def __get_basic_syslinux_config(self, **args): + return """ +default %(menu)s +timeout %(timeout)d + +%(background)s +menu title Welcome to %(distroname)s! +menu color border 0 #ffffffff #00000000 +menu color sel 7 #ff000000 #ffffffff +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 clear +""" % args + + def __get_image_stanza(self, is_xen, isDracut, **args): + if isDracut: + args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args + else: + args["rootlabel"] = "CDLABEL=%(fslabel)s" % args + if not is_xen: + template = """label %(short)s + menu label %(long)s + kernel vmlinuz%(index)s + append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s +""" + else: + template = """label %(short)s + menu label %(long)s + kernel mboot.c32 + append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s --- initrd%(index)s.img +""" + return template % args + + def __get_image_stanzas(self, isodir): + versions = [] + kernels = self._get_kernel_versions() + for kernel in kernels: + for version in kernels[kernel]: + versions.append(version) + + if not versions: + raise CreatorError("Unable to find valid kernels, " + "please check the repo") + + kernel_options = self._get_kernel_options() + + """ menu can be customized highly, the format is: + + short_name1:long_name1:extra_opts1;short_name2:long_name2:extra_opts2 + + e.g.: autoinst:InstallationOnly:systemd.unit=installer-graphical.service + but in order to keep compatible with old format, these are still ok: + + liveinst autoinst + liveinst;autoinst + liveinst::;autoinst:: + """ + oldmenus = {"basic": { + "short": "basic", + "long": "Installation Only (Text based)", + "extra": "basic nosplash 4" + }, + "liveinst": { + "short": "liveinst", + "long": "Installation Only", + "extra": "liveinst nosplash 4" + }, + "autoinst": { + "short": "autoinst", + "long": "Autoinstall (Deletes all existing content)", + "extra": "autoinst nosplash 4" + }, + "netinst": { + "short": "netinst", + "long": "Network Installation", + "extra": "netinst 4" + }, + "verify": { + "short": "check", + "long": "Verify and", + "extra": "check" + } + } + menu_options = self._get_menu_options() + menus = menu_options.split(";") + for i in range(len(menus)): + menus[i] = menus[i].split(":") + if len(menus) == 1 and len(menus[0]) == 1: + """ Keep compatible with the old usage way """ + menus = menu_options.split() + for i in range(len(menus)): + menus[i] = [menus[i]] + + cfg = "" + + default_version = None + default_index = None + index = "0" + netinst = None + for version in versions: + (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index) + if index == "0": + self._isDracut = isDracut + + default = self.__is_default_kernel(kernel, kernels) + + if default: + long = "Boot %s" % self.distro_name + elif kernel.startswith("kernel-"): + long = "Boot %s(%s)" % (self.name, kernel[7:]) + else: + long = "Boot %s(%s)" % (self.name, kernel) + + oldmenus["verify"]["long"] = "%s %s" % (oldmenus["verify"]["long"], + long) + # tell dracut not to ask for LUKS passwords or activate mdraid sets + if isDracut: + kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0" + else: + kern_opts = kernel_options + + cfg += self.__get_image_stanza(is_xen, isDracut, + fslabel = self.fslabel, + liveargs = kern_opts, + long = long, + short = "linux" + index, + extra = "", + index = index) + + if default: + cfg += "menu default\n" + default_version = version + default_index = index + + for menu in menus: + if not menu[0]: + continue + short = menu[0] + index + + if len(menu) >= 2: + long = menu[1] + else: + if menu[0] in oldmenus.keys(): + if menu[0] == "verify" and not self._has_checkisomd5(): + continue + if menu[0] == "netinst": + netinst = oldmenus[menu[0]] + continue + long = oldmenus[menu[0]]["long"] + extra = oldmenus[menu[0]]["extra"] + else: + long = short.upper() + " X" + index + extra = "" + + if len(menu) >= 3: + extra = menu[2] + + cfg += self.__get_image_stanza(is_xen, isDracut, + fslabel = self.fslabel, + liveargs = kernel_options, + long = long, + short = short, + extra = extra, + index = index) + + index = str(int(index) + 1) + + if not default_version: + default_version = versions[0] + if not default_index: + default_index = "0" + + if netinst: + cfg += self.__get_image_stanza(is_xen, isDracut, + fslabel = self.fslabel, + liveargs = kernel_options, + long = netinst["long"], + short = netinst["short"], + extra = netinst["extra"], + index = default_index) + + return cfg + + def __get_memtest_stanza(self, isodir): + memtest = glob.glob(self._instroot + "/boot/memtest86*") + if not memtest: + return "" + + shutil.copyfile(memtest[0], isodir + "/isolinux/memtest") + + return """label memtest + menu label Memory Test + kernel memtest +""" + + def __get_local_stanza(self, isodir): + return """label local + menu label Boot from local drive + localboot 0xffff +""" + + def _configure_syslinux_bootloader(self, isodir): + """configure the boot loader""" + fs_related.makedirs(isodir + "/isolinux") + + menu = self.__find_syslinux_menu() + + self.__copy_syslinux_files(isodir, menu, + self.__find_syslinux_mboot()) + + background = "" + if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"): + background = "menu background splash.jpg" + + cfg = self.__get_basic_syslinux_config(menu = menu, + background = background, + name = self.name, + timeout = self._timeout * 10, + distroname = self.distro_name) + + cfg += self.__get_image_stanzas(isodir) + cfg += self.__get_memtest_stanza(isodir) + cfg += self.__get_local_stanza(isodir) + cfg += self._get_isolinux_stanzas(isodir) + + cfgf = open(isodir + "/isolinux/isolinux.cfg", "w") + cfgf.write(cfg) + cfgf.close() + + def __copy_efi_files(self, isodir): + if not os.path.exists(self._instroot + "/boot/efi/EFI/redhat/grub.efi"): + return False + shutil.copy(self._instroot + "/boot/efi/EFI/redhat/grub.efi", + isodir + "/EFI/boot/grub.efi") + shutil.copy(self._instroot + "/boot/grub/splash.xpm.gz", + isodir + "/EFI/boot/splash.xpm.gz") + + return True + + def __get_basic_efi_config(self, **args): + return """ +default=0 +splashimage=/EFI/boot/splash.xpm.gz +timeout %(timeout)d +hiddenmenu + +""" %args + + def __get_efi_image_stanza(self, **args): + return """title %(long)s + kernel /EFI/boot/vmlinuz%(index)s root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s + initrd /EFI/boot/initrd%(index)s.img +""" %args + + def __get_efi_image_stanzas(self, isodir, name): + # FIXME: this only supports one kernel right now... + + kernel_options = self._get_kernel_options() + checkisomd5 = self._has_checkisomd5() + + cfg = "" + + for index in range(0, 9): + # we don't support xen kernels + if os.path.exists("%s/EFI/boot/xen%d.gz" %(isodir, index)): + continue + cfg += self.__get_efi_image_stanza(fslabel = self.fslabel, + liveargs = kernel_options, + long = name, + extra = "", index = index) + if checkisomd5: + cfg += self.__get_efi_image_stanza( + fslabel = self.fslabel, + liveargs = kernel_options, + long = "Verify and Boot " + name, + extra = "check", + index = index) + break + + return cfg + + def _configure_efi_bootloader(self, isodir): + """Set up the configuration for an EFI bootloader""" + fs_related.makedirs(isodir + "/EFI/boot") + + if not self.__copy_efi_files(isodir): + shutil.rmtree(isodir + "/EFI") + return + + for f in os.listdir(isodir + "/isolinux"): + os.link("%s/isolinux/%s" %(isodir, f), + "%s/EFI/boot/%s" %(isodir, f)) + + + cfg = self.__get_basic_efi_config(name = self.name, + timeout = self._timeout) + cfg += self.__get_efi_image_stanzas(isodir, self.name) + + cfgf = open(isodir + "/EFI/boot/grub.conf", "w") + cfgf.write(cfg) + cfgf.close() + + # first gen mactel machines get the bootloader name wrong apparently + if rpmmisc.getBaseArch() == "i386": + os.link(isodir + "/EFI/boot/grub.efi", + isodir + "/EFI/boot/boot.efi") + os.link(isodir + "/EFI/boot/grub.conf", + isodir + "/EFI/boot/boot.conf") + + # for most things, we want them named boot$efiarch + efiarch = {"i386": "ia32", "x86_64": "x64"} + efiname = efiarch[rpmmisc.getBaseArch()] + os.rename(isodir + "/EFI/boot/grub.efi", + isodir + "/EFI/boot/boot%s.efi" %(efiname,)) + os.link(isodir + "/EFI/boot/grub.conf", + isodir + "/EFI/boot/boot%s.conf" %(efiname,)) + + + def _configure_bootloader(self, isodir): + self._configure_syslinux_bootloader(isodir) + self._configure_efi_bootloader(isodir) + +arch = rpmmisc.getBaseArch() +if arch in ("i386", "x86_64"): + LiveCDImageCreator = x86LiveImageCreator +elif arch.startswith("arm"): + LiveCDImageCreator = LiveImageCreatorBase +else: + raise CreatorError("Architecture not supported!") diff --git a/scripts/lib/mic/imager/liveusb.py b/scripts/lib/mic/imager/liveusb.py new file mode 100644 index 0000000000..a909928a4c --- /dev/null +++ b/scripts/lib/mic/imager/liveusb.py @@ -0,0 +1,308 @@ +#!/usr/bin/python -tt +# +# Copyright (c) 2011 Intel, Inc. +# +# 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 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 shutil +import re + +from mic import msger +from mic.utils import misc, fs_related, runner +from mic.utils.errors import CreatorError, MountError +from mic.utils.partitionedfs import PartitionedMount +from mic.imager.livecd import LiveCDImageCreator + + +class LiveUSBImageCreator(LiveCDImageCreator): + def __init__(self, *args): + LiveCDImageCreator.__init__(self, *args) + + self._dep_checks.extend(["kpartx", "parted"]) + + # remove dependency of genisoimage in parent class + if "genisoimage" in self._dep_checks: + self._dep_checks.remove("genisoimage") + + def _create_usbimg(self, isodir): + overlaysizemb = 64 #default + #skipcompress = self.skip_compression? + fstype = "vfat" + homesizemb=0 + swapsizemb=0 + homefile="home.img" + plussize=128 + kernelargs=None + + if fstype == 'vfat': + if overlaysizemb > 2047: + raise CreatorError("Can't have an overlay of 2048MB or " + "greater on VFAT") + + if homesizemb > 2047: + raise CreatorError("Can't have an home overlay of 2048MB or " + "greater on VFAT") + + if swapsizemb > 2047: + raise CreatorError("Can't have an swap overlay of 2048MB or " + "greater on VFAT") + + livesize = misc.get_file_size(isodir + "/LiveOS") + + usbimgsize = (overlaysizemb + \ + homesizemb + \ + swapsizemb + \ + livesize + \ + plussize) * 1024L * 1024L + + disk = fs_related.SparseLoopbackDisk("%s/%s.usbimg" \ + % (self._outdir, self.name), + usbimgsize) + usbmnt = self._mkdtemp("usb-mnt") + usbloop = PartitionedMount(usbmnt) + usbloop.add_disk('/dev/sdb', disk) + + usbloop.add_partition(usbimgsize/1024/1024, + "/dev/sdb", + "/", + fstype, + boot=True) + + usbloop.mount() + + try: + fs_related.makedirs(usbmnt + "/LiveOS") + + if os.path.exists(isodir + "/LiveOS/squashfs.img"): + shutil.copyfile(isodir + "/LiveOS/squashfs.img", + usbmnt + "/LiveOS/squashfs.img") + else: + fs_related.mksquashfs(os.path.dirname(self._image), + usbmnt + "/LiveOS/squashfs.img") + + if os.path.exists(isodir + "/LiveOS/osmin.img"): + shutil.copyfile(isodir + "/LiveOS/osmin.img", + usbmnt + "/LiveOS/osmin.img") + + if fstype == "vfat" or fstype == "msdos": + uuid = usbloop.partitions[0]['mount'].uuid + label = usbloop.partitions[0]['mount'].fslabel + usblabel = "UUID=%s-%s" % (uuid[0:4], uuid[4:8]) + overlaysuffix = "-%s-%s-%s" % (label, uuid[0:4], uuid[4:8]) + else: + diskmount = usbloop.partitions[0]['mount'] + usblabel = "UUID=%s" % diskmount.uuid + overlaysuffix = "-%s-%s" % (diskmount.fslabel, diskmount.uuid) + + args = ['cp', "-Rf", isodir + "/isolinux", usbmnt + "/syslinux"] + rc = runner.show(args) + if rc: + raise CreatorError("Can't copy isolinux directory %s" \ + % (isodir + "/isolinux/*")) + + if os.path.isfile("/usr/share/syslinux/isolinux.bin"): + syslinux_path = "/usr/share/syslinux" + elif os.path.isfile("/usr/lib/syslinux/isolinux.bin"): + syslinux_path = "/usr/lib/syslinux" + else: + raise CreatorError("syslinux not installed : " + "cannot find syslinux installation path") + + for f in ("isolinux.bin", "vesamenu.c32"): + path = os.path.join(syslinux_path, f) + if os.path.isfile(path): + args = ['cp', path, usbmnt + "/syslinux/"] + rc = runner.show(args) + if rc: + raise CreatorError("Can't copy syslinux file " + path) + else: + raise CreatorError("syslinux not installed: " + "syslinux file %s not found" % path) + + fd = open(isodir + "/isolinux/isolinux.cfg", "r") + text = fd.read() + fd.close() + pattern = re.compile('CDLABEL=[^ ]*') + text = pattern.sub(usblabel, text) + pattern = re.compile('rootfstype=[^ ]*') + text = pattern.sub("rootfstype=" + fstype, text) + if kernelargs: + text = text.replace("rd.live.image", "rd.live.image " + kernelargs) + + if overlaysizemb > 0: + msger.info("Initializing persistent overlay file") + overfile = "overlay" + overlaysuffix + if fstype == "vfat": + args = ['dd', + "if=/dev/zero", + "of=" + usbmnt + "/LiveOS/" + overfile, + "count=%d" % overlaysizemb, + "bs=1M"] + else: + args = ['dd', + "if=/dev/null", + "of=" + usbmnt + "/LiveOS/" + overfile, + "count=1", + "bs=1M", + "seek=%d" % overlaysizemb] + rc = runner.show(args) + if rc: + raise CreatorError("Can't create overlay file") + text = text.replace("rd.live.image", "rd.live.image rd.live.overlay=" + usblabel) + text = text.replace(" ro ", " rw ") + + if swapsizemb > 0: + msger.info("Initializing swap file") + swapfile = usbmnt + "/LiveOS/" + "swap.img" + args = ['dd', + "if=/dev/zero", + "of=" + swapfile, + "count=%d" % swapsizemb, + "bs=1M"] + rc = runner.show(args) + if rc: + raise CreatorError("Can't create swap file") + args = ["mkswap", "-f", swapfile] + rc = runner.show(args) + if rc: + raise CreatorError("Can't mkswap on swap file") + + if homesizemb > 0: + msger.info("Initializing persistent /home") + homefile = usbmnt + "/LiveOS/" + homefile + if fstype == "vfat": + args = ['dd', + "if=/dev/zero", + "of=" + homefile, + "count=%d" % homesizemb, + "bs=1M"] + else: + args = ['dd', + "if=/dev/null", + "of=" + homefile, + "count=1", + "bs=1M", + "seek=%d" % homesizemb] + rc = runner.show(args) + if rc: + raise CreatorError("Can't create home file") + + mkfscmd = fs_related.find_binary_path("/sbin/mkfs." + fstype) + if fstype == "ext2" or fstype == "ext3": + args = [mkfscmd, "-F", "-j", homefile] + else: + args = [mkfscmd, homefile] + rc = runner.show(args) + if rc: + raise CreatorError("Can't mke2fs home file") + if fstype == "ext2" or fstype == "ext3": + tune2fs = fs_related.find_binary_path("tune2fs") + args = [tune2fs, + "-c0", + "-i0", + "-ouser_xattr,acl", + homefile] + rc = runner.show(args) + if rc: + raise CreatorError("Can't tune2fs home file") + + if fstype == "vfat" or fstype == "msdos": + syslinuxcmd = fs_related.find_binary_path("syslinux") + syslinuxcfg = usbmnt + "/syslinux/syslinux.cfg" + args = [syslinuxcmd, + "-d", + "syslinux", + usbloop.partitions[0]["device"]] + + elif fstype == "ext2" or fstype == "ext3": + extlinuxcmd = fs_related.find_binary_path("extlinux") + syslinuxcfg = usbmnt + "/syslinux/extlinux.conf" + args = [extlinuxcmd, + "-i", + usbmnt + "/syslinux"] + + else: + raise CreatorError("Invalid file system type: %s" % (fstype)) + + os.unlink(usbmnt + "/syslinux/isolinux.cfg") + fd = open(syslinuxcfg, "w") + fd.write(text) + fd.close() + rc = runner.show(args) + if rc: + raise CreatorError("Can't install boot loader.") + + finally: + usbloop.unmount() + usbloop.cleanup() + + # Need to do this after image is unmounted and device mapper is closed + msger.info("set MBR") + mbrfile = "/usr/lib/syslinux/mbr.bin" + if not os.path.exists(mbrfile): + mbrfile = "/usr/share/syslinux/mbr.bin" + if not os.path.exists(mbrfile): + raise CreatorError("mbr.bin file didn't exist.") + mbrsize = os.path.getsize(mbrfile) + outimg = "%s/%s.usbimg" % (self._outdir, self.name) + + args = ['dd', + "if=" + mbrfile, + "of=" + outimg, + "seek=0", + "conv=notrunc", + "bs=1", + "count=%d" % (mbrsize)] + rc = runner.show(args) + if rc: + raise CreatorError("Can't set MBR.") + + def _stage_final_image(self): + try: + isodir = self._get_isodir() + fs_related.makedirs(isodir + "/LiveOS") + + minimal_size = self._resparse() + + if not self.skip_minimize: + fs_related.create_image_minimizer(isodir + "/LiveOS/osmin.img", + self._image, + minimal_size) + + if self.skip_compression: + shutil.move(self._image, + isodir + "/LiveOS/ext3fs.img") + else: + fs_related.makedirs(os.path.join( + os.path.dirname(self._image), + "LiveOS")) + shutil.move(self._image, + os.path.join(os.path.dirname(self._image), + "LiveOS", "ext3fs.img")) + fs_related.mksquashfs(os.path.dirname(self._image), + isodir + "/LiveOS/squashfs.img") + + self._create_usbimg(isodir) + + if self.pack_to: + usbimg = os.path.join(self._outdir, self.name + ".usbimg") + packimg = os.path.join(self._outdir, self.pack_to) + misc.packing(packimg, usbimg) + os.unlink(usbimg) + + finally: + shutil.rmtree(isodir, ignore_errors = True) + self._set_isodir(None) + diff --git a/scripts/lib/mic/imager/loop.py b/scripts/lib/mic/imager/loop.py new file mode 100644 index 0000000000..4d05ef271d --- /dev/null +++ b/scripts/lib/mic/imager/loop.py @@ -0,0 +1,418 @@ +#!/usr/bin/python -tt +# +# Copyright (c) 2011 Intel, Inc. +# +# 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 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 glob +import shutil + +from mic import kickstart, msger +from mic.utils.errors import CreatorError, MountError +from mic.utils import misc, runner, fs_related as fs +from mic.imager.baseimager import BaseImageCreator + + +# The maximum string length supported for LoopImageCreator.fslabel +FSLABEL_MAXLEN = 32 + + +def save_mountpoints(fpath, loops, arch = None): + """Save mount points mapping to file + + :fpath, the xml file to store partition info + :loops, dict of partition info + :arch, image arch + """ + + if not fpath or not loops: + return + + from xml.dom import minidom + doc = minidom.Document() + imgroot = doc.createElement("image") + doc.appendChild(imgroot) + if arch: + imgroot.setAttribute('arch', arch) + for loop in loops: + part = doc.createElement("partition") + imgroot.appendChild(part) + for (key, val) in loop.items(): + if isinstance(val, fs.Mount): + continue + part.setAttribute(key, str(val)) + + with open(fpath, 'w') as wf: + wf.write(doc.toprettyxml(indent=' ')) + + return + +def load_mountpoints(fpath): + """Load mount points mapping from file + + :fpath, file path to load + """ + + if not fpath: + return + + from xml.dom import minidom + mount_maps = [] + with open(fpath, 'r') as rf: + dom = minidom.parse(rf) + imgroot = dom.documentElement + for part in imgroot.getElementsByTagName("partition"): + p = dict(part.attributes.items()) + + try: + mp = (p['mountpoint'], p['label'], p['name'], + int(p['size']), p['fstype']) + except KeyError: + msger.warning("Wrong format line in file: %s" % fpath) + except ValueError: + msger.warning("Invalid size '%s' in file: %s" % (p['size'], fpath)) + else: + mount_maps.append(mp) + + return mount_maps + +class LoopImageCreator(BaseImageCreator): + """Installs a system into a loopback-mountable filesystem image. + + LoopImageCreator is a straightforward ImageCreator subclass; the system + is installed into an ext3 filesystem on a sparse file which can be + subsequently loopback-mounted. + + When specifying multiple partitions in kickstart file, each partition + will be created as a separated loop image. + """ + + def __init__(self, creatoropts=None, pkgmgr=None, + compress_image=None, + shrink_image=False): + """Initialize a LoopImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__() + with the addition of: + + fslabel -- A string used as a label for any filesystems created. + """ + + BaseImageCreator.__init__(self, creatoropts, pkgmgr) + + self.compress_image = compress_image + self.shrink_image = shrink_image + + self.__fslabel = None + self.fslabel = self.name + + self.__blocksize = 4096 + if self.ks: + self.__fstype = kickstart.get_image_fstype(self.ks, + "ext3") + self.__fsopts = kickstart.get_image_fsopts(self.ks, + "defaults,noatime") + + allloops = [] + for part in sorted(kickstart.get_partitions(self.ks), + key=lambda p: p.mountpoint): + if part.fstype == "swap": + continue + + label = part.label + mp = part.mountpoint + if mp == '/': + # the base image + if not label: + label = self.name + else: + mp = mp.rstrip('/') + if not label: + msger.warning('no "label" specified for loop img at %s' + ', use the mountpoint as the name' % mp) + label = mp.split('/')[-1] + + imgname = misc.strip_end(label, '.img') + '.img' + allloops.append({ + 'mountpoint': mp, + 'label': label, + 'name': imgname, + 'size': part.size or 4096L * 1024 * 1024, + 'fstype': part.fstype or 'ext3', + 'extopts': part.extopts or None, + 'loop': None, # to be created in _mount_instroot + }) + self._instloops = allloops + + else: + self.__fstype = None + self.__fsopts = None + self._instloops = [] + + self.__imgdir = None + + if self.ks: + self.__image_size = kickstart.get_image_size(self.ks, + 4096L * 1024 * 1024) + else: + self.__image_size = 0 + + self._img_name = self.name + ".img" + + def get_image_names(self): + if not self._instloops: + return None + + return [lo['name'] for lo in self._instloops] + + def _set_fstype(self, fstype): + self.__fstype = fstype + + def _set_image_size(self, imgsize): + self.__image_size = imgsize + + + # + # Properties + # + def __get_fslabel(self): + if self.__fslabel is None: + return self.name + else: + return self.__fslabel + def __set_fslabel(self, val): + if val is None: + self.__fslabel = None + else: + self.__fslabel = val[:FSLABEL_MAXLEN] + #A string used to label any filesystems created. + # + #Some filesystems impose a constraint on the maximum allowed size of the + #filesystem label. In the case of ext3 it's 16 characters, but in the case + #of ISO9660 it's 32 characters. + # + #mke2fs silently truncates the label, but mkisofs aborts if the label is + #too long. So, for convenience sake, any string assigned to this attribute + #is silently truncated to FSLABEL_MAXLEN (32) characters. + fslabel = property(__get_fslabel, __set_fslabel) + + def __get_image(self): + if self.__imgdir is None: + raise CreatorError("_image is not valid before calling mount()") + return os.path.join(self.__imgdir, self._img_name) + #The location of the image file. + # + #This is the path to the filesystem image. Subclasses may use this path + #in order to package the image in _stage_final_image(). + # + #Note, this directory does not exist before ImageCreator.mount() is called. + # + #Note also, this is a read-only attribute. + _image = property(__get_image) + + def __get_blocksize(self): + return self.__blocksize + def __set_blocksize(self, val): + if self._instloops: + raise CreatorError("_blocksize must be set before calling mount()") + try: + self.__blocksize = int(val) + except ValueError: + raise CreatorError("'%s' is not a valid integer value " + "for _blocksize" % val) + #The block size used by the image's filesystem. + # + #This is the block size used when creating the filesystem image. Subclasses + #may change this if they wish to use something other than a 4k block size. + # + #Note, this attribute may only be set before calling mount(). + _blocksize = property(__get_blocksize, __set_blocksize) + + def __get_fstype(self): + return self.__fstype + def __set_fstype(self, val): + if val != "ext2" and val != "ext3": + raise CreatorError("Unknown _fstype '%s' supplied" % val) + self.__fstype = val + #The type of filesystem used for the image. + # + #This is the filesystem type used when creating the filesystem image. + #Subclasses may change this if they wish to use something other ext3. + # + #Note, only ext2 and ext3 are currently supported. + # + #Note also, this attribute may only be set before calling mount(). + _fstype = property(__get_fstype, __set_fstype) + + def __get_fsopts(self): + return self.__fsopts + def __set_fsopts(self, val): + self.__fsopts = val + #Mount options of filesystem used for the image. + # + #This can be specified by --fsoptions="xxx,yyy" in part command in + #kickstart file. + _fsopts = property(__get_fsopts, __set_fsopts) + + + # + # Helpers for subclasses + # + def _resparse(self, size=None): + """Rebuild the filesystem image to be as sparse as possible. + + This method should be used by subclasses when staging the final image + in order to reduce the actual space taken up by the sparse image file + to be as little as possible. + + This is done by resizing the filesystem to the minimal size (thereby + eliminating any space taken up by deleted files) and then resizing it + back to the supplied size. + + size -- the size in, in bytes, which the filesystem image should be + resized to after it has been minimized; this defaults to None, + causing the original size specified by the kickstart file to + be used (or 4GiB if not specified in the kickstart). + """ + minsize = 0 + for item in self._instloops: + if item['name'] == self._img_name: + minsize = item['loop'].resparse(size) + else: + item['loop'].resparse(size) + + return minsize + + def _base_on(self, base_on=None): + if base_on and self._image != base_on: + shutil.copyfile(base_on, self._image) + + def _check_imgdir(self): + if self.__imgdir is None: + self.__imgdir = self._mkdtemp() + + + # + # Actual implementation + # + def _mount_instroot(self, base_on=None): + + if base_on and os.path.isfile(base_on): + self.__imgdir = os.path.dirname(base_on) + imgname = os.path.basename(base_on) + self._base_on(base_on) + self._set_image_size(misc.get_file_size(self._image)) + + # here, self._instloops must be [] + self._instloops.append({ + "mountpoint": "/", + "label": self.name, + "name": imgname, + "size": self.__image_size or 4096L, + "fstype": self.__fstype or "ext3", + "extopts": None, + "loop": None + }) + + self._check_imgdir() + + for loop in self._instloops: + fstype = loop['fstype'] + mp = os.path.join(self._instroot, loop['mountpoint'].lstrip('/')) + size = loop['size'] * 1024L * 1024L + imgname = loop['name'] + + if fstype in ("ext2", "ext3", "ext4"): + MyDiskMount = fs.ExtDiskMount + elif fstype == "btrfs": + MyDiskMount = fs.BtrfsDiskMount + elif fstype in ("vfat", "msdos"): + MyDiskMount = fs.VfatDiskMount + else: + msger.error('Cannot support fstype: %s' % fstype) + + loop['loop'] = MyDiskMount(fs.SparseLoopbackDisk( + os.path.join(self.__imgdir, imgname), + size), + mp, + fstype, + self._blocksize, + loop['label']) + + if fstype in ("ext2", "ext3", "ext4"): + loop['loop'].extopts = loop['extopts'] + + try: + msger.verbose('Mounting image "%s" on "%s"' % (imgname, mp)) + fs.makedirs(mp) + loop['loop'].mount() + except MountError, e: + raise + + def _unmount_instroot(self): + for item in reversed(self._instloops): + try: + item['loop'].cleanup() + except: + pass + + def _stage_final_image(self): + + if self.pack_to or self.shrink_image: + self._resparse(0) + else: + self._resparse() + + for item in self._instloops: + imgfile = os.path.join(self.__imgdir, item['name']) + if item['fstype'] == "ext4": + runner.show('/sbin/tune2fs -O ^huge_file,extents,uninit_bg %s ' + % imgfile) + if self.compress_image: + misc.compressing(imgfile, self.compress_image) + + if not self.pack_to: + for item in os.listdir(self.__imgdir): + shutil.move(os.path.join(self.__imgdir, item), + os.path.join(self._outdir, item)) + else: + msger.info("Pack all loop images together to %s" % self.pack_to) + dstfile = os.path.join(self._outdir, self.pack_to) + misc.packing(dstfile, self.__imgdir) + + if self.pack_to: + mountfp_xml = os.path.splitext(self.pack_to)[0] + mountfp_xml = misc.strip_end(mountfp_xml, '.tar') + ".xml" + else: + mountfp_xml = self.name + ".xml" + # save mount points mapping file to xml + save_mountpoints(os.path.join(self._outdir, mountfp_xml), + self._instloops, + self.target_arch) + + def copy_attachment(self): + if not hasattr(self, '_attachment') or not self._attachment: + return + + self._check_imgdir() + + msger.info("Copying attachment files...") + for item in self._attachment: + if not os.path.exists(item): + continue + dpath = os.path.join(self.__imgdir, os.path.basename(item)) + msger.verbose("Copy attachment %s to %s" % (item, dpath)) + shutil.copy(item, dpath) + diff --git a/scripts/lib/mic/imager/raw.py b/scripts/lib/mic/imager/raw.py new file mode 100644 index 0000000000..838191a6f1 --- /dev/null +++ b/scripts/lib/mic/imager/raw.py @@ -0,0 +1,501 @@ +#!/usr/bin/python -tt +# +# Copyright (c) 2011 Intel, Inc. +# +# 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 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 stat +import shutil + +from mic import kickstart, msger +from mic.utils import fs_related, runner, misc +from mic.utils.partitionedfs import PartitionedMount +from mic.utils.errors import CreatorError, MountError +from mic.imager.baseimager import BaseImageCreator + + +class RawImageCreator(BaseImageCreator): + """Installs a system into a file containing a partitioned disk image. + + ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file + is formatted with a partition table, each partition loopback mounted + and the system installed into an virtual disk. The disk image can + subsequently be booted in a virtual machine or accessed with kpartx + """ + + def __init__(self, creatoropts=None, pkgmgr=None, compress_image=None, generate_bmap=None, fstab_entry="uuid"): + """Initialize a ApplianceImageCreator instance. + + This method takes the same arguments as ImageCreator.__init__() + """ + BaseImageCreator.__init__(self, creatoropts, pkgmgr) + + self.__instloop = None + self.__imgdir = None + self.__disks = {} + self.__disk_format = "raw" + self._disk_names = [] + self._ptable_format = self.ks.handler.bootloader.ptable + self.vmem = 512 + self.vcpu = 1 + self.checksum = False + self.use_uuid = fstab_entry == "uuid" + self.appliance_version = None + self.appliance_release = None + self.compress_image = compress_image + self.bmap_needed = generate_bmap + self._need_extlinux = not kickstart.use_installerfw(self.ks, "extlinux") + #self.getsource = False + #self.listpkg = False + + self._dep_checks.extend(["sync", "kpartx", "parted"]) + if self._need_extlinux: + self._dep_checks.extend(["extlinux"]) + + def configure(self, repodata = None): + import subprocess + def chroot(): + os.chroot(self._instroot) + os.chdir("/") + + if os.path.exists(self._instroot + "/usr/bin/Xorg"): + subprocess.call(["/bin/chmod", "u+s", "/usr/bin/Xorg"], + preexec_fn = chroot) + + BaseImageCreator.configure(self, repodata) + + def _get_fstab(self): + if kickstart.use_installerfw(self.ks, "fstab"): + # The fstab file will be generated by installer framework scripts + # instead. + return None + + s = "" + for mp in self.__instloop.mountOrder: + p = None + for p1 in self.__instloop.partitions: + if p1['mountpoint'] == mp: + p = p1 + break + + if self.use_uuid and p['uuid']: + device = "UUID=%s" % p['uuid'] + else: + device = "/dev/%s%-d" % (p['disk_name'], p['num']) + + s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % { + 'device': device, + 'mountpoint': p['mountpoint'], + 'fstype': p['fstype'], + 'fsopts': "defaults,noatime" if not p['fsopts'] else p['fsopts']} + + if p['mountpoint'] == "/": + for subvol in self.__instloop.subvolumes: + if subvol['mountpoint'] == "/": + continue + s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % { + 'device': "/dev/%s%-d" % (p['disk_name'], p['num']), + 'mountpoint': subvol['mountpoint'], + 'fstype': p['fstype'], + 'fsopts': "defaults,noatime" if not subvol['fsopts'] else subvol['fsopts']} + + s += "devpts /dev/pts devpts gid=5,mode=620 0 0\n" + s += "tmpfs /dev/shm tmpfs defaults 0 0\n" + s += "proc /proc proc defaults 0 0\n" + s += "sysfs /sys sysfs defaults 0 0\n" + return s + + def _create_mkinitrd_config(self): + """write to tell which modules to be included in initrd""" + + mkinitrd = "" + mkinitrd += "PROBE=\"no\"\n" + mkinitrd += "MODULES+=\"ext3 ata_piix sd_mod libata scsi_mod\"\n" + mkinitrd += "rootfs=\"ext3\"\n" + mkinitrd += "rootopts=\"defaults\"\n" + + msger.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" \ + % self._instroot) + os.makedirs(self._instroot + "/etc/sysconfig/",mode=644) + cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w") + cfg.write(mkinitrd) + cfg.close() + + def _get_parts(self): + if not self.ks: + raise CreatorError("Failed to get partition info, " + "please check your kickstart setting.") + + # Set a default partition if no partition is given out + if not self.ks.handler.partition.partitions: + partstr = "part / --size 1900 --ondisk sda --fstype=ext3" + args = partstr.split() + pd = self.ks.handler.partition.parse(args[1:]) + if pd not in self.ks.handler.partition.partitions: + self.ks.handler.partition.partitions.append(pd) + + # partitions list from kickstart file + return kickstart.get_partitions(self.ks) + + def get_disk_names(self): + """ Returns a list of physical target disk names (e.g., 'sdb') which + will be created. """ + + if self._disk_names: + return self._disk_names + + #get partition info from ks handler + parts = self._get_parts() + + for i in range(len(parts)): + if parts[i].disk: + disk_name = parts[i].disk + else: + raise CreatorError("Failed to create disks, no --ondisk " + "specified in partition line of ks file") + + if parts[i].mountpoint and not parts[i].fstype: + raise CreatorError("Failed to create disks, no --fstype " + "specified for partition with mountpoint " + "'%s' in the ks file") + + self._disk_names.append(disk_name) + + return self._disk_names + + def _full_name(self, name, extention): + """ Construct full file name for a file we generate. """ + return "%s-%s.%s" % (self.name, name, extention) + + def _full_path(self, path, name, extention): + """ Construct full file path to a file we generate. """ + return os.path.join(path, self._full_name(name, extention)) + + # + # Actual implemention + # + def _mount_instroot(self, base_on = None): + parts = self._get_parts() + self.__instloop = PartitionedMount(self._instroot) + + for p in parts: + self.__instloop.add_partition(int(p.size), + p.disk, + p.mountpoint, + p.fstype, + p.label, + fsopts = p.fsopts, + boot = p.active, + align = p.align, + part_type = p.part_type) + + self.__instloop.layout_partitions(self._ptable_format) + + # Create the disks + self.__imgdir = self._mkdtemp() + for disk_name, disk in self.__instloop.disks.items(): + full_path = self._full_path(self.__imgdir, disk_name, "raw") + msger.debug("Adding disk %s as %s with size %s bytes" \ + % (disk_name, full_path, disk['min_size'])) + + disk_obj = fs_related.SparseLoopbackDisk(full_path, + disk['min_size']) + self.__disks[disk_name] = disk_obj + self.__instloop.add_disk(disk_name, disk_obj) + + self.__instloop.mount() + self._create_mkinitrd_config() + + def _get_required_packages(self): + required_packages = BaseImageCreator._get_required_packages(self) + if self._need_extlinux: + if not self.target_arch or not self.target_arch.startswith("arm"): + required_packages += ["syslinux", "syslinux-extlinux"] + return required_packages + + def _get_excluded_packages(self): + return BaseImageCreator._get_excluded_packages(self) + + def _get_syslinux_boot_config(self): + rootdev = None + root_part_uuid = None + for p in self.__instloop.partitions: + if p['mountpoint'] == "/": + rootdev = "/dev/%s%-d" % (p['disk_name'], p['num']) + root_part_uuid = p['partuuid'] + + return (rootdev, root_part_uuid) + + def _create_syslinux_config(self): + + splash = os.path.join(self._instroot, "boot/extlinux") + if os.path.exists(splash): + splashline = "menu background splash.jpg" + else: + splashline = "" + + (rootdev, root_part_uuid) = self._get_syslinux_boot_config() + options = self.ks.handler.bootloader.appendLine + + #XXX don't hardcode default kernel - see livecd code + syslinux_conf = "" + syslinux_conf += "prompt 0\n" + syslinux_conf += "timeout 1\n" + syslinux_conf += "\n" + syslinux_conf += "default vesamenu.c32\n" + syslinux_conf += "menu autoboot Starting %s...\n" % self.distro_name + syslinux_conf += "menu hidden\n" + syslinux_conf += "\n" + syslinux_conf += "%s\n" % splashline + syslinux_conf += "menu title Welcome to %s!\n" % self.distro_name + syslinux_conf += "menu color border 0 #ffffffff #00000000\n" + syslinux_conf += "menu color sel 7 #ffffffff #ff000000\n" + syslinux_conf += "menu color title 0 #ffffffff #00000000\n" + syslinux_conf += "menu color tabmsg 0 #ffffffff #00000000\n" + syslinux_conf += "menu color unsel 0 #ffffffff #00000000\n" + syslinux_conf += "menu color hotsel 0 #ff000000 #ffffffff\n" + syslinux_conf += "menu color hotkey 7 #ffffffff #ff000000\n" + syslinux_conf += "menu color timeout_msg 0 #ffffffff #00000000\n" + syslinux_conf += "menu color timeout 0 #ffffffff #00000000\n" + syslinux_conf += "menu color cmdline 0 #ffffffff #00000000\n" + + versions = [] + kernels = self._get_kernel_versions() + symkern = "%s/boot/vmlinuz" % self._instroot + + if os.path.lexists(symkern): + v = os.path.realpath(symkern).replace('%s-' % symkern, "") + syslinux_conf += "label %s\n" % self.distro_name.lower() + syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v) + syslinux_conf += "\tlinux ../vmlinuz\n" + if self._ptable_format == 'msdos': + rootstr = rootdev + else: + if not root_part_uuid: + raise MountError("Cannot find the root GPT partition UUID") + rootstr = "PARTUUID=%s" % root_part_uuid + syslinux_conf += "\tappend ro root=%s %s\n" % (rootstr, options) + syslinux_conf += "\tmenu default\n" + else: + for kernel in kernels: + for version in kernels[kernel]: + versions.append(version) + + footlabel = 0 + for v in versions: + syslinux_conf += "label %s%d\n" \ + % (self.distro_name.lower(), footlabel) + syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v) + syslinux_conf += "\tlinux ../vmlinuz-%s\n" % v + syslinux_conf += "\tappend ro root=%s %s\n" \ + % (rootdev, options) + if footlabel == 0: + syslinux_conf += "\tmenu default\n" + footlabel += 1; + + msger.debug("Writing syslinux config %s/boot/extlinux/extlinux.conf" \ + % self._instroot) + cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w") + cfg.write(syslinux_conf) + cfg.close() + + def _install_syslinux(self): + for name in self.__disks.keys(): + loopdev = self.__disks[name].device + + # Set MBR + mbrfile = "%s/usr/share/syslinux/" % self._instroot + if self._ptable_format == 'gpt': + mbrfile += "gptmbr.bin" + else: + mbrfile += "mbr.bin" + + msger.debug("Installing syslinux bootloader '%s' to %s" % \ + (mbrfile, loopdev)) + + mbrsize = os.stat(mbrfile)[stat.ST_SIZE] + rc = runner.show(['dd', 'if=%s' % mbrfile, 'of=' + loopdev]) + if rc != 0: + raise MountError("Unable to set MBR to %s" % loopdev) + + + # Ensure all data is flushed to disk before doing syslinux install + runner.quiet('sync') + + fullpathsyslinux = fs_related.find_binary_path("extlinux") + rc = runner.show([fullpathsyslinux, + "-i", + "%s/boot/extlinux" % self._instroot]) + if rc != 0: + raise MountError("Unable to install syslinux bootloader to %s" \ + % loopdev) + + def _create_bootconfig(self): + #If syslinux is available do the required configurations. + if self._need_extlinux \ + and os.path.exists("%s/usr/share/syslinux/" % (self._instroot)) \ + and os.path.exists("%s/boot/extlinux/" % (self._instroot)): + self._create_syslinux_config() + self._install_syslinux() + + def _unmount_instroot(self): + if not self.__instloop is None: + try: + self.__instloop.cleanup() + except MountError, err: + msger.warning("%s" % err) + + def _resparse(self, size = None): + return self.__instloop.resparse(size) + + def _get_post_scripts_env(self, in_chroot): + env = BaseImageCreator._get_post_scripts_env(self, in_chroot) + + # Export the file-system UUIDs and partition UUIDs (AKA PARTUUIDs) + for p in self.__instloop.partitions: + env.update(self._set_part_env(p['ks_pnum'], "UUID", p['uuid'])) + env.update(self._set_part_env(p['ks_pnum'], "PARTUUID", p['partuuid'])) + + return env + + def _stage_final_image(self): + """Stage the final system image in _outdir. + write meta data + """ + self._resparse() + + if self.compress_image: + for imgfile in os.listdir(self.__imgdir): + if imgfile.endswith('.raw') or imgfile.endswith('bin'): + imgpath = os.path.join(self.__imgdir, imgfile) + misc.compressing(imgpath, self.compress_image) + + if self.pack_to: + dst = os.path.join(self._outdir, self.pack_to) + msger.info("Pack all raw images to %s" % dst) + misc.packing(dst, self.__imgdir) + else: + msger.debug("moving disks to stage location") + for imgfile in os.listdir(self.__imgdir): + src = os.path.join(self.__imgdir, imgfile) + dst = os.path.join(self._outdir, imgfile) + msger.debug("moving %s to %s" % (src,dst)) + shutil.move(src,dst) + self._write_image_xml() + + def _write_image_xml(self): + imgarch = "i686" + if self.target_arch and self.target_arch.startswith("arm"): + imgarch = "arm" + xml = "<image>\n" + + name_attributes = "" + if self.appliance_version: + name_attributes += " version='%s'" % self.appliance_version + if self.appliance_release: + name_attributes += " release='%s'" % self.appliance_release + xml += " <name%s>%s</name>\n" % (name_attributes, self.name) + xml += " <domain>\n" + # XXX don't hardcode - determine based on the kernel we installed for + # grub baremetal vs xen + xml += " <boot type='hvm'>\n" + xml += " <guest>\n" + xml += " <arch>%s</arch>\n" % imgarch + xml += " </guest>\n" + xml += " <os>\n" + xml += " <loader dev='hd'/>\n" + xml += " </os>\n" + + i = 0 + for name in self.__disks.keys(): + full_name = self._full_name(name, self.__disk_format) + xml += " <drive disk='%s' target='hd%s'/>\n" \ + % (full_name, chr(ord('a') + i)) + i = i + 1 + + xml += " </boot>\n" + xml += " <devices>\n" + xml += " <vcpu>%s</vcpu>\n" % self.vcpu + xml += " <memory>%d</memory>\n" %(self.vmem * 1024) + for network in self.ks.handler.network.network: + xml += " <interface/>\n" + xml += " <graphics/>\n" + xml += " </devices>\n" + xml += " </domain>\n" + xml += " <storage>\n" + + if self.checksum is True: + for name in self.__disks.keys(): + diskpath = self._full_path(self._outdir, name, \ + self.__disk_format) + full_name = self._full_name(name, self.__disk_format) + + msger.debug("Generating disk signature for %s" % full_name) + + xml += " <disk file='%s' use='system' format='%s'>\n" \ + % (full_name, self.__disk_format) + + hashes = misc.calc_hashes(diskpath, ('sha1', 'sha256')) + + xml += " <checksum type='sha1'>%s</checksum>\n" \ + % hashes[0] + xml += " <checksum type='sha256'>%s</checksum>\n" \ + % hashes[1] + xml += " </disk>\n" + else: + for name in self.__disks.keys(): + full_name = self._full_name(name, self.__disk_format) + xml += " <disk file='%s' use='system' format='%s'/>\n" \ + % (full_name, self.__disk_format) + + xml += " </storage>\n" + xml += "</image>\n" + + msger.debug("writing image XML to %s/%s.xml" %(self._outdir, self.name)) + cfg = open("%s/%s.xml" % (self._outdir, self.name), "w") + cfg.write(xml) + cfg.close() + + def generate_bmap(self): + """ Generate block map file for the image. The idea is that while disk + images we generate may be large (e.g., 4GiB), they may actually contain + only little real data, e.g., 512MiB. This data are files, directories, + file-system meta-data, partition table, etc. In other words, when + flashing the image to the target device, you do not have to copy all the + 4GiB of data, you can copy only 512MiB of it, which is 4 times faster. + + This function generates the block map file for an arbitrary image that + mic has generated. The block map file is basically an XML file which + contains a list of blocks which have to be copied to the target device. + The other blocks are not used and there is no need to copy them. """ + + if self.bmap_needed is None: + return + + from mic.utils import BmapCreate + msger.info("Generating the map file(s)") + + for name in self.__disks.keys(): + image = self._full_path(self.__imgdir, name, self.__disk_format) + bmap_file = self._full_path(self._outdir, name, "bmap") + + msger.debug("Generating block map file '%s'" % bmap_file) + + try: + creator = BmapCreate.BmapCreate(image, bmap_file) + creator.generate() + del creator + except BmapCreate.Error as err: + raise CreatorError("Failed to create bmap file: %s" % str(err)) |