def mkfakediskimg(disk_img): """Create a fake partitioned disk image :param disk_img: Full path to a partitioned disk image :type disk_img: str :returns: True if it was successful, False if something went wrong Include /boot, swap, and / partitions with fake kernel and /etc/passwd """ try: mksparse(disk_img, 42 * 1024**2) # Make a /boot, / and swap partitions on it dev = parted.getDevice(disk_img) disk = parted.freshDisk(dev, "gpt") # (start, length, flags, name) for start, length, flags, name in [ ( 1024**2, 1024**2, None, "boot"), (2*1024**2, 2*1024**2, parted.PARTITION_SWAP, "swap"), (4*1024**2, 38*1024**2, None, "root")]: geo = parted.Geometry(device=dev, start=start//dev.sectorSize, length=length//dev.sectorSize) part = parted.Partition(disk=disk, type=parted.PARTITION_NORMAL, geometry=geo) part.getPedPartition().set_name(name) disk.addPartition(partition=part) if flags: part.setFlag(flags) disk.commit() os.sync() except parted.PartedException: return False # Mount the disk's partitions loop_devs = kpartx_disk_img(disk_img) try: # Format the partitions runcmd(["mkfs.ext4", "/dev/mapper/" + loop_devs[0][0]]) runcmd(["mkswap", "/dev/mapper/" + loop_devs[1][0]]) runcmd(["mkfs.ext4", "/dev/mapper/" + loop_devs[2][0]]) # Mount the boot partition and make a fake kernel and initrd boot_mnt = mount("/dev/mapper/" + loop_devs[0][0]) try: mkfakebootdir(boot_mnt) finally: umount(boot_mnt) # Mount the / partition and make a fake / filesystem with /etc/passwd root_mnt = mount("/dev/mapper/" + loop_devs[2][0]) try: mkfakerootdir(root_mnt) finally: umount(root_mnt) except Exception: return False finally: # Remove the disk's mounted partitions runcmd(["kpartx", "-d", "-s", disk_img]) return True
def mount_boot_part_over_root(img_mount): """ Mount boot partition to /boot of root fs mounted in img_mount Used for OSTree so it finds deployment configurations on live rootfs param img_mount: object with mounted disk image root partition type img_mount: imgutils.PartitionMount """ root_dir = img_mount.mount_dir is_boot_part = lambda dir: os.path.exists(dir + "/loader.0") tmp_mount_dir = tempfile.mkdtemp(prefix="lmc-tmpdir-") sysroot_boot_dir = None for dev, _size in img_mount.loop_devices: if dev is img_mount.mount_dev: continue try: mount("/dev/mapper/" + dev, mnt=tmp_mount_dir) if is_boot_part(tmp_mount_dir): umount(tmp_mount_dir) sysroot_boot_dir = joinpaths(root_dir, "boot") mount("/dev/mapper/" + dev, mnt=sysroot_boot_dir) break else: umount(tmp_mount_dir) except subprocess.CalledProcessError as e: log.debug("Looking for boot partition error: %s", e) remove(tmp_mount_dir) return sysroot_boot_dir
def rebuild_initrds_for_live(opts, sys_root_dir, results_dir): """ Rebuild intrds for pxe live image (root=live:http://) :param opts: options passed to livemedia-creator :type opts: argparse options :param str sys_root_dir: Path to root of the system :param str results_dir: Path of directory for storing results """ # cmdline dracut args override the defaults, but need to be parsed log.info("dracut args = %s", dracut_args(opts)) dracut = ["dracut", "--nomdadmconf", "--nolvmconf"] + dracut_args(opts) kdir = "boot" if opts.ostree: kernels_dir = glob.glob(joinpaths(sys_root_dir, "boot/ostree/*")) if kernels_dir: kdir = os.path.relpath(kernels_dir[0], sys_root_dir) kernels = [kernel for kernel in findkernels(sys_root_dir, kdir)] if not kernels: raise Exception("No initrds found, cannot rebuild_initrds") if opts.ostree: # Dracut assumes to have some dirs in disk image # /var/tmp for temp files vartmp_dir = joinpaths(sys_root_dir, "var/tmp") if not os.path.isdir(vartmp_dir): os.mkdir(vartmp_dir) # /root (maybe not fatal) root_dir = joinpaths(sys_root_dir, "var/roothome") if not os.path.isdir(root_dir): os.mkdir(root_dir) # /tmp (maybe not fatal) tmp_dir = joinpaths(sys_root_dir, "sysroot/tmp") if not os.path.isdir(tmp_dir): os.mkdir(tmp_dir) # Write the new initramfs directly to the results directory os.mkdir(joinpaths(sys_root_dir, "results")) mount(results_dir, opts="bind", mnt=joinpaths(sys_root_dir, "results")) # Dracut runs out of space inside the minimal rootfs image mount("/var/tmp", opts="bind", mnt=joinpaths(sys_root_dir, "var/tmp")) for kernel in kernels: if hasattr(kernel, "initrd"): outfile = os.path.basename(kernel.initrd.path) else: # Construct an initrd from the kernel name outfile = os.path.basename(kernel.path.replace("vmlinuz-", "initrd-") + ".img") log.info("rebuilding %s", outfile) log.info("dracut warnings about /proc are safe to ignore") kver = kernel.version cmd = dracut + ["/results/"+outfile, kver] runcmd(cmd, root=sys_root_dir) shutil.copy2(joinpaths(sys_root_dir, kernel.path), results_dir) umount(joinpaths(sys_root_dir, "var/tmp"), delete=False) umount(joinpaths(sys_root_dir, "results"), delete=False)
def __init__(self, iso_path, initrd_path=None): """ Mount the iso :param str iso_path: Path to the iso to mount :param str initrd_path: Optional path to initrd initrd_path can be used to point to a tree with a newer initrd.img than the iso has. The iso is still used for stage2. self.kernel and self.initrd point to the kernel and initrd. self.stage2 is set to True if there is a stage2 image. self.repo is the path to the mounted iso if there is a /repodata dir. """ self.label = None self.iso_path = iso_path self.initrd_path = initrd_path if not self.initrd_path: self.mount_dir = mount(self.iso_path, opts="loop") else: self.mount_dir = self.initrd_path kernel_list = [("/isolinux/vmlinuz", "/isolinux/initrd.img"), ("/ppc/ppc64/vmlinuz", "/ppc/ppc64/initrd.img"), ("/images/pxeboot/vmlinuz", "/images/pxeboot/initrd.img"), ("/images/kernel.img", "/images/initrd.img")] if os.path.isdir(self.mount_dir + "/repodata"): self.repo = self.mount_dir else: self.repo = None self.stage2 = os.path.exists(self.mount_dir+"/LiveOS/squashfs.img") or \ os.path.exists(self.mount_dir+"/images/install.img") try: for kernel, initrd in kernel_list: if (os.path.isfile(self.mount_dir + kernel) and os.path.isfile(self.mount_dir + initrd)): self.kernel = self.mount_dir + kernel self.initrd = self.mount_dir + initrd break else: raise Exception("Missing kernel and initrd file in iso, failed" " to search under: {0}".format(kernel_list)) except: self.umount() raise self.get_iso_label()
def __init__(self, iso_path, initrd_path=None): """ Mount the iso :param str iso_path: Path to the iso to mount :param str initrd_path: Optional path to initrd initrd_path can be used to point to a tree with a newer initrd.img than the iso has. The iso is still used for stage2. self.kernel and self.initrd point to the kernel and initrd. self.stage2 is set to True if there is a stage2 image. self.repo is the path to the mounted iso if there is a /repodata dir. """ self.label = None self.iso_path = iso_path self.initrd_path = initrd_path if not self.initrd_path: self.mount_dir = mount(self.iso_path, opts="loop") else: self.mount_dir = self.initrd_path kernel_list = [("/isolinux/vmlinuz", "/isolinux/initrd.img"), ("/ppc/ppc64/vmlinuz", "/ppc/ppc64/initrd.img")] if os.path.isdir(self.mount_dir+"/repodata"): self.repo = self.mount_dir else: self.repo = None self.stage2 = os.path.exists(self.mount_dir+"/LiveOS/squashfs.img") or \ os.path.exists(self.mount_dir+"/images/install.img") try: for kernel, initrd in kernel_list: if (os.path.isfile(self.mount_dir+kernel) and os.path.isfile(self.mount_dir+initrd)): self.kernel = self.mount_dir+kernel self.initrd = self.mount_dir+initrd break else: raise Exception("Missing kernel and initrd file in iso, failed" " to search under: {0}".format(kernel_list)) except: self.umount() raise self.get_iso_label()
def __init__(self, iso_path, initrd_path=None): """ Mount the iso :param str iso_path: Path to the iso to mount :param str initrd_path: Optional path to initrd initrd_path can be used to point to a tree with a newer initrd.img than the iso has. The iso is still used for stage2. self.kernel and self.initrd point to the kernel and initrd. self.stage2 is set to True if there is a stage2 image. self.repo is the path to the mounted iso if there is a /repodata dir. """ self.label = None self.iso_path = iso_path self.initrd_path = initrd_path if not self.initrd_path: self.mount_dir = mount(self.iso_path, opts="loop") else: self.mount_dir = self.initrd_path self.kernel = self.mount_dir+"/isolinux/vmlinuz" self.initrd = self.mount_dir+"/isolinux/initrd.img" if os.path.isdir(self.mount_dir+"/repodata"): self.repo = self.mount_dir else: self.repo = None self.stage2 = os.path.exists(self.mount_dir+"/LiveOS/squashfs.img") or \ os.path.exists(self.mount_dir+"/images/install.img") try: for f in [self.kernel, self.initrd]: if not os.path.isfile(f): raise Exception("Missing file on iso: {0}".format(f)) except: self.umount() raise self.get_iso_label()
def __init__(self, iso_path, initrd_path=None): """ Mount the iso :param str iso_path: Path to the iso to mount :param str initrd_path: Optional path to initrd initrd_path can be used to point to a tree with a newer initrd.img than the iso has. The iso is still used for stage2. self.kernel and self.initrd point to the kernel and initrd. self.stage2 is set to True if there is a stage2 image. self.repo is the path to the mounted iso if there is a /repodata dir. """ self.label = None self.iso_path = iso_path self.initrd_path = initrd_path if not self.initrd_path: self.mount_dir = mount(self.iso_path, opts="loop") else: self.mount_dir = self.initrd_path self.kernel = self.mount_dir + "/isolinux/vmlinuz" self.initrd = self.mount_dir + "/isolinux/initrd.img" if os.path.isdir(self.mount_dir + "/repodata"): self.repo = self.mount_dir else: self.repo = None self.stage2 = os.path.exists(self.mount_dir+"/LiveOS/squashfs.img") or \ os.path.exists(self.mount_dir+"/images/install.img") try: for f in [self.kernel, self.initrd]: if not os.path.isfile(f): raise Exception("Missing file on iso: {0}".format(f)) except: self.umount() raise self.get_iso_label()
def novirt_install(opts, disk_img, disk_size, cancel_func=None): """ Use Anaconda to install to a disk image :param opts: options passed to livemedia-creator :type opts: argparse options :param str disk_img: The full path to the disk image to be created :param int disk_size: The size of the disk_img in MiB :param cancel_func: Function that returns True to cancel build :type cancel_func: function This method runs anaconda to create the image and then based on the opts passed creates a qemu disk image or tarfile. """ dirinstall_path = ROOT_PATH # Clean up /tmp/ from previous runs to prevent stale info from being used for path in ["/tmp/yum.repos.d/", "/tmp/yum.cache/"]: if os.path.isdir(path): shutil.rmtree(path) args = ["--kickstart", opts.ks[0], "--cmdline", "--loglevel", "debug"] if opts.anaconda_args: for arg in opts.anaconda_args: args += arg.split(" ", 1) if opts.proxy: args += ["--proxy", opts.proxy] if opts.armplatform: args += ["--armplatform", opts.armplatform] if opts.make_iso or opts.make_fsimage or opts.make_pxe_live: # Make a blank fs image args += ["--dirinstall"] mkext4img(None, disk_img, label=opts.fs_label, size=disk_size * 1024**2) if not os.path.isdir(dirinstall_path): os.mkdir(dirinstall_path) mount(disk_img, opts="loop", mnt=dirinstall_path) elif opts.make_tar or opts.make_oci: # Install under dirinstall_path, make sure it starts clean if os.path.exists(dirinstall_path): shutil.rmtree(dirinstall_path) if opts.make_oci: # OCI installs under /rootfs/ dirinstall_path = joinpaths(dirinstall_path, "rootfs") args += ["--dirinstall", dirinstall_path] else: args += ["--dirinstall"] os.makedirs(dirinstall_path) else: args += ["--image", disk_img] # Create the sparse image mksparse(disk_img, disk_size * 1024**2) log_monitor = LogMonitor(timeout=opts.timeout) args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)] cancel_funcs = [log_monitor.server.log_check] if cancel_func is not None: cancel_funcs.append(cancel_func) # Make sure anaconda has the right product and release log.info("Running anaconda.") try: unshare_args = [ "--pid", "--kill-child", "--mount", "--propagation", "unchanged", "anaconda" ] + args for line in execReadlines( "unshare", unshare_args, reset_lang=False, env_add={ "ANACONDA_PRODUCTNAME": opts.project, "ANACONDA_PRODUCTVERSION": opts.releasever }, callback=lambda p: not novirt_cancel_check(cancel_funcs, p)): log.info(line) # Make sure the new filesystem is correctly labeled setfiles_args = [ "-e", "/proc", "-e", "/sys", "/etc/selinux/targeted/contexts/files/file_contexts", "/" ] if "--dirinstall" in args: # setfiles may not be available, warn instead of fail try: execWithRedirect("setfiles", setfiles_args, root=dirinstall_path) except (subprocess.CalledProcessError, OSError) as e: log.warning("Running setfiles on install tree failed: %s", str(e)) else: with PartitionMount(disk_img) as img_mount: if img_mount and img_mount.mount_dir: try: execWithRedirect("setfiles", setfiles_args, root=img_mount.mount_dir) except (subprocess.CalledProcessError, OSError) as e: log.warning( "Running setfiles on install tree failed: %s", str(e)) # For image installs, run fstrim to discard unused blocks. This way # unused blocks do not need to be allocated for sparse image types execWithRedirect("fstrim", [img_mount.mount_dir]) except (subprocess.CalledProcessError, OSError) as e: log.error("Running anaconda failed: %s", e) raise InstallError("novirt_install failed") finally: log_monitor.shutdown() # Move the anaconda logs over to a log directory log_dir = os.path.abspath(os.path.dirname(opts.logfile)) log_anaconda = joinpaths(log_dir, "anaconda") if not os.path.isdir(log_anaconda): os.mkdir(log_anaconda) for l in glob.glob("/tmp/*log") + glob.glob("/tmp/anaconda-tb-*"): shutil.copy2(l, log_anaconda) os.unlink(l) # Make sure any leftover anaconda mounts have been cleaned up if not anaconda_cleanup(dirinstall_path): raise InstallError( "novirt_install cleanup of anaconda mounts failed.") if not opts.make_iso and not opts.make_fsimage and not opts.make_pxe_live: dm_name = os.path.splitext(os.path.basename(disk_img))[0] # Remove device-mapper for partitions and disk log.debug("Removing device-mapper setup on %s", dm_name) for d in sorted(glob.glob("/dev/mapper/" + dm_name + "*"), reverse=True): dm_detach(d) log.debug("Removing loop device for %s", disk_img) loop_detach("/dev/" + get_loop_name(disk_img)) # qemu disk image is used by bare qcow2 images and by Vagrant if opts.image_type: log.info("Converting %s to %s", disk_img, opts.image_type) qemu_args = [] for arg in opts.qemu_args: qemu_args += arg.split(" ", 1) # convert the image to the selected format if "-O" not in qemu_args: qemu_args.extend(["-O", opts.image_type]) qemu_img = tempfile.mktemp(prefix="lmc-disk-", suffix=".img") execWithRedirect("qemu-img", ["convert"] + qemu_args + [disk_img, qemu_img], raise_err=True) if not opts.make_vagrant: execWithRedirect("mv", ["-f", qemu_img, disk_img], raise_err=True) else: # Take the new qcow2 image and package it up for Vagrant compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) vagrant_dir = tempfile.mkdtemp(prefix="lmc-tmpdir-") metadata_path = joinpaths(vagrant_dir, "metadata.json") execWithRedirect( "mv", ["-f", qemu_img, joinpaths(vagrant_dir, "box.img")], raise_err=True) if opts.vagrant_metadata: shutil.copy2(opts.vagrant_metadata, metadata_path) else: create_vagrant_metadata(metadata_path) update_vagrant_metadata(metadata_path, disk_size) if opts.vagrantfile: shutil.copy2(opts.vagrantfile, joinpaths(vagrant_dir, "vagrantfile")) log.info("Creating Vagrant image") rc = mktar(vagrant_dir, disk_img, opts.compression, compress_args, selinux=False) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) shutil.rmtree(vagrant_dir) elif opts.make_tar: compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) rc = mktar(dirinstall_path, disk_img, opts.compression, compress_args) shutil.rmtree(dirinstall_path) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) elif opts.make_oci: # An OCI image places the filesystem under /rootfs/ and adds the json files at the top # And then creates a tar of the whole thing. compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) shutil.copy2(opts.oci_config, ROOT_PATH) shutil.copy2(opts.oci_runtime, ROOT_PATH) rc = mktar(ROOT_PATH, disk_img, opts.compression, compress_args) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) else: # For raw disk images, use fallocate to deallocate unused space execWithRedirect("fallocate", ["--dig-holes", disk_img], raise_err=True)
def make_live_images(opts, work_dir, disk_img): """ Create live images from direcory or rootfs image :param opts: options passed to livemedia-creator :type opts: argparse options :param str work_dir: Directory for storing results :param str disk_img: Path to disk image (fsimage or partitioned) :returns: Path of directory with created images or None :rtype: str fsck.ext4 is run on the rootfs_image to make sure there are no errors and to zero out any deleted blocks to make it compress better. If this fails for any reason it will return None and log the error. """ sys_root = "" squashfs_root_dir = joinpaths(work_dir, "squashfs_root") liveos_dir = joinpaths(squashfs_root_dir, "LiveOS") os.makedirs(liveos_dir) rootfs_img = joinpaths(liveos_dir, "rootfs.img") if opts.fs_image or opts.no_virt: # Find the ostree root in the fsimage if opts.ostree: with Mount(disk_img, opts="loop") as mnt_dir: sys_root = find_ostree_root(mnt_dir) # Try to hardlink the image, if that fails, copy it rc = execWithRedirect("/bin/ln", [disk_img, rootfs_img]) if rc != 0: shutil.copy2(disk_img, rootfs_img) else: is_root_part = None if opts.ostree: is_root_part = lambda dir: os.path.exists(dir + "/ostree/deploy") with PartitionMount(disk_img, mount_ok=is_root_part) as img_mount: if img_mount and img_mount.mount_dir: try: mounted_sysroot_boot_dir = None if opts.ostree: sys_root = find_ostree_root(img_mount.mount_dir) mounted_sysroot_boot_dir = mount_boot_part_over_root( img_mount) if opts.live_rootfs_keep_size: size = img_mount.mount_size / 1024**3 else: size = opts.live_rootfs_size or None log.info("Creating live rootfs image") mkrootfsimg(img_mount.mount_dir, rootfs_img, "LiveOS", size=size, sysroot=sys_root) finally: if mounted_sysroot_boot_dir: umount(mounted_sysroot_boot_dir) log.debug("sys_root = %s", sys_root) # Make sure free blocks are actually zeroed so it will compress rc = execWithRedirect("/usr/sbin/fsck.ext4", ["-y", "-f", "-E", "discard", rootfs_img]) if rc != 0: log.error("Problem zeroing free blocks of %s", disk_img) return None log.info("Packing live rootfs image") add_pxe_args = [] live_image_name = "live-rootfs.squashfs.img" compression, compressargs = squashfs_args(opts) mksquashfs(squashfs_root_dir, joinpaths(work_dir, live_image_name), compression, compressargs) log.info("Rebuilding initramfs for live") with Mount(rootfs_img, opts="loop") as mnt_dir: try: mount(joinpaths(mnt_dir, "boot"), opts="bind", mnt=joinpaths(mnt_dir, sys_root, "boot")) rebuild_initrds_for_live(opts, joinpaths(mnt_dir, sys_root), work_dir) finally: umount(joinpaths(mnt_dir, sys_root, "boot"), delete=False) remove(squashfs_root_dir) if opts.ostree: add_pxe_args.append("ostree=/%s" % sys_root) template = joinpaths(opts.lorax_templates, "pxe-live/pxe-config.tmpl") create_pxe_config(template, work_dir, live_image_name, add_pxe_args) return work_dir