#!/usr/bin/python3
# coding: utf-8
#
# Copyright 2009, Red Hat, Inc.
# Copyright 2012-2021, Sugar Labs®
# Superceding edit-livecd, written by Perry Myers <pmyers at redhat.com>
#                                   & David Huff <dhuff at redhat.com>
# Cloning code, OverlayFS, and filesystem editing added by Frederick Grose,
#                                                     <fgrose at sugarlabs.org>
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

doc1 = '''
              editliveos [options] <LiveOS_source>

              Edit a LiveOS image to merge a persistent overlay, run software
              updates including kernel and initial RAM filesystem updates,
              insert files, clone a customized instance, adjust the root or
              home filesystem or overlay sizes, seclude private or user-
              specific files, rebuild the image into a new .iso image
              distribution file, and refresh the source's persistent filesystem
              overlay.'''
doc2 = '''
 USAGE HELP   editliveos -h, -?, --help

              LiveOS_source may be entered as "live" to edit or clone the
              currently running LiveOS image.  An attached LiveOS loaded
              device may be edited or cloned through its node id, such as
              /dev/sdc1, or, if it is mounted, through its mount point path.
              An .iso image file is addressed through its pathname, such as
              /path/to/build.iso, or, if mounted, through the mount point
              directory path.  A Live CD-ROM image is addressed through its
              device node, such as /dev/sr0.  Even a directory containing a
              LiveOS and iso/syslinux folders with the appropriate files can
              be edited or cloned through that parent directory path.

              The --clone option copies the home.img filesystem to the new
              build .iso file, allowing user customizations to be replicated.
              A version of livecd-iso-to-disk with the --copy-home loading
              option may be used to propagate clones.

              Other files and folders in the source device's outer filesystem
              may be included in the new build.  Options are provided
              to --include, --exclude, and --seclude files or folders for the
              new build.  The new builds are branded to distinguish them with
              the --name, --builder, and --releasefile options.

              Storage space requirements for stageing the build files are
              estimated by the script and compared to the space available in
              the -t <TMPDIR> option (default = /var/tmp).

              Invoke the --help option to learn about other optional features.
              '''
import os
import sys
import stat
import glob
import shutil
import logging
import tempfile
import argparse
import subprocess

from imgcreate.fs import *
from imgcreate.live import *
from imgcreate.debug import *
from imgcreate.errors import *
from imgcreate.creator import *
from imgcreate import kickstart

t0 = time.time()
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
                                 add_help=False, usage=doc1 + '''

              options:  [-n, --name <name>]
                        [-o, --output <output directory>]
                        [-k, --kickstart <kickstart-file>]
                        [-S, --skip-SELinux]
                        [-s, --script <script.sh>]
                        [-N, --noshell]
                        [-t, --tmpdir <tmpdir>]
                        [-f, --dnfcache, -y, --yumcache <cachedir>]
                        [-e, --exclude <exclude s>]
                        [-i, --include <include s>]
                        [-u, --seclude <seclude s>]
                        [-r, --releasefile <releasefile s>]
                        [-b, --builder <builder>]
                        [-p, --plugins]
                        [--clone]
                        [--rootfsimg <file path>]
                        [--overlay [<devspec>:]<pathspec>]
                        [--refresh-only]
                        [--skip-refresh]
                        [--skip-seclude]
                        [-c, --compress-type <compression arg s>]
                        [--compress]
                        [--releasever <value to substitute for $releasever in repo urls>]
                        [--arch <image arch>]
                        [--skip-compression]
                        [--refresh-uncompressed]
                        [--flatten-squashfs]
                        [--rootfs-size-gb [+|-]<size>[,<fstype>[,<blksz>]]]
                        [--home-size-mb [+|-]<size>[,<fstype>[,<blksz>]]]
                        [--encrypted-home]
                        [--unencrypted-home]
                        [--overlay-size-mb [+|-]<size>[,<fstype>[,<blksz>]]]
                        [-a, --extra-kernel-args <arg s>]
                        [--extra-space-mb <size>]
                        [--cacheonly]
                        [--nocleanup]
                        ''' + doc2)

parser.add_argument('liveos', metavar='ISO|DEVICE|DIR', help='The source '
                    'ISO.iso or LiveOS device or directory path.')

parser.add_argument('-h', '-?', '--help', action='help',
                    help='Show this help message and exit.\n ')

parser.add_argument('-n', '--name', help="Name for the new LiveOS image (don't"
                    ' include .iso,\nit will be added.)  Unless the name is '
                    'prefixed with\na colon, :, the build will be branded '
                    'with\na <date-builder-Remix-releasename> string,\nand'
                    ' the .iso will be named as NAME-arch-Ymd.HM\n'
                    "If the name contains the os separator '" + os.sep + "', "
                    'that\ncharacter will be replaced by the underscore_\n'
                    'in the .iso file name and filesystem label.\n ')

parser.add_argument('-o', '--output', metavar='PATH',
                    help='Specify a directory location for the new .iso file.'
                         '\n ')

parser.add_argument('-k', '--kickstart', dest='kscfg', metavar='PATH',
                    help='Path or URL to kickstart config file.\n ')

parser.add_argument('-S', '--skip-SELinux', dest='force_selinux',
                    action='store_false', default=True,
                    help='Skip setting SELinux attributes on install root.\n ')

parser.add_argument('-s', '--script', metavar='PATH',
                    help='Specify a script to run chrooted in the LiveOS\n'
                         'filesystem hierarchy.  A bind mount to the host\n'
                         'filesystem root has been prepared at /run/hostroot\n'
                         'in the chroot for easy access to host files.\n ')

parser.add_argument('-N', '--noshell', dest='shell', action='store_false',
                    default=True,
                    help='Specify not breaking to a command shell in the edit.'
                         '\n ')

parser.add_argument('-t', '--tmpdir', default='/var/tmp', metavar='PATH',
                    help='Temporary directory to use for staging the build.\n'
                         'default = /var/tmp\n ')

parser.add_argument('-f', '--dnfcache', '-y', '--yumcache', dest='cachedir', 
                    metavar='PATH',
                    help='Directory to use for for the DNF or Yum cache.\n'
                         'Bind mounts to /var/cache/dnf and /var/cache/yum\n'
                         'will be created.    default = None  (A temporary\n'
                         'filesystem cache will be used.)\n ')

parser.add_argument('-e', '--exclude', dest='excludes', metavar='PATH',
    help='A string of filename patterns to exclude from the copy\nof the '
    'outer device filesystem.  See _copy_src_root().\nDenote multiple '
    'patterns as "pattern1 pattern2 ..."\nThe default is to exclude all '
    'device content except for\nthe iso/syslinux, EFI, and LiveOS directories. '
    'Entering\nanything to be excluded will have the effect of\nincluding '
    'everything else but the excluded items.\n ')

parser.add_argument('-i', '--include', dest='includes', metavar='PATH',
    help='A string of file or directory paths to copy to the .iso\nfile in '
    '_copy_src_root().  Denote multiple files as\n"path1 path2 ..."  '
    '(The paths are referenced\nrelative to the source mount directory, '
    'either\n/mnt/live/ or /run/initramfs/live for a running\nLiveOS, or '
    '/TMPDIR/editliveos-<random>/mp-src-<random>\nfor an attached device '
    'or .iso file.\nSo ../../../<mount point>/INCLUDE or\n'
    '../../../../<mount point>/INCLUDE may be used,\nrespectively, to '
    'include files from other branches of\nthe active hierarchy.)\n ')

parser.add_argument('-u', '--seclude', dest='secludes', metavar='PATH',
    help='A string of file or directory paths in the LiveOS\nfilesystem to '
    'seclude from the final build\nconfiguration. The user directory may '
    'be denoted with\n~/.  Denote multiple files as "path1 path2 ..."\n'
    'By default, specific user configuration, password, &\nlog files are '
    'secluded from the packaged iso image.\nSee seclude_from_isoimage() '
    'and scrub_system() in the\ncode for the specific files.\n ')

parser.add_argument('-r', '--releasefile', metavar='PATH',
                    help='Specify release file/s for branding the build.\n'
                         'Denote multiple files as "path1 path2 ..."\n ')

parser.add_argument('-b', '--builder', default=os.getenv('USERNAME'),
                    help='Specify the builder of a Remix.\n'
                         'default = "' + os.getenv('USERNAME') + '" (the '
                         'current system user)\n ')

parser.add_argument('--clone', action='store_true', default=False,
                    help='Specify copying of the home.img filesystem or\nthe'
                         ' /home folder contents to the new .iso file.\n ')

parser.add_argument('--rootfsimg', metavar='PATH',
                    help='Specify a non-standard path to the base root\n'
                         'filesystem relative to the <LiveOS_source>.'
                         '\n ')

parser.add_argument('--overlay', metavar='[DEVSPEC:]PATHSPEC',
                    help='Specify a non-standard device or path to the LiveOS'
                         '\noverlay.\n ')

parser.add_argument('--rootfs-size-gb', dest='rootfs_size',
                    metavar='[+|-]SIZE[,FSTYPE[,BLKSZ]]',
                    help='Specifies a new size of NN GiB for the image (or\n'
                    'changing the size by a difference of +NN or -NN GiB,\n'
                    'if a sign is prefixed.  If a minus -NN value is specified\n'
                    'along with a ,FSTYPE, use an equal sign like this,\n'
                    '--rootfs-size-gb=-NN,FSTYPE.)  This is useful when\n'
                    'a larger image is needed for a script, or to support\n'
                    'the installation of extra packages or updates in the\n'
                    'chroot shell.  To change the filesystem type but not\n'
                    'the size, enter +0,FSTYPE.  ext[234], xfs, btrfs, & f2fs\n'
                    'are supported for FSTYPE.\n ')

parser.add_argument('--home-size-mb', metavar='[+|-]SIZE[,FSTYPE[,BLKSZ]]',
                    help='Specify copying of the /home folder contents into\n'
                    'a home.img filesystem of size NN MiB, or changing\nthe'
                    ' size of an existing home.img filesystem to NN MiB\n'
                    '(or by a difference of +NN or -NN MiB, if a sign is\n'
                    'prefixed.  If a minus -NN value is specified along with\n'
                    'a ,FSTYPE, use an equal sign like this,\n'
                    '--home-size-mb=-NN,FSTYPE.)  If NN = 0 (or nets to\n'
                    '<= 0), the home.img filesystem contents will be shifted\n'
                    'to the /home folder, which is normally compressed;\n'
                    'however, subsequently, it will not have access to the\n'
                    '--encrypted-home option of livecd-iso-to-disk.\nIf '
                    'the selected or calculated size is insufficient\nfor the '
                    'current home contents, the edit will be\nadjusted to a '
                    'size 1/8th larger than the\ncurrent home space usage.\n'
                    '\nOptionally, one may specify the home fileystem type\n'
                    'and block size by appending ,FSTYPE,BLKSZ to this\n'
                    'argument.  To change the filesystem type or block size,\n'
                    'first remove the home.img filesystem by specifying a\n'
                    'size of 0, then process the image a second time with\n'
                    'the desired size, fstype, & blksz.  The default values\n'
                    'are ext4 and 4096 bytes.\n ')

parser.add_argument('--encrypted-home', dest='EncHomeReq',
                    action='store_true', default=None,
                    help='Specify LUKS encryption for a new home.img '
                    'filesystem.\nIf this status is not specified on the '
                    'commandline,\nit will be set to the state of home.img '
                    'at program\nlaunch.\n ')

parser.add_argument('--unencrypted-home', dest='EncHomeReq',
                    action='store_false', default=None,
                    help='Specify no LUKS encryption for the home.img '
                         'filesystem.\n ')

parser.add_argument('--overlay-size-mb', metavar='[+|-]SIZE[,FSTYPE[,BLKSZ]]',
                    help='Specifies a new size of NN MiB for the overlay file'
                    '\n(or changing the size by a difference of +NN or'
                    '\n-NN MiB, if a size is prefixed. If a minus -NN value is'
                    '\nspecified along with a ,FSTYPE, use an equal sign like'
                    '\nthis, --overlay-size-mb=-NN,FSTYPE.)  If NN = 0\n'
                    '(or nets to <= 0), no change will be made.\n\n'
                    'Optionally, for OverlayFS overlays on vfat-formatted\n'
                    'devices, one may specify the overlay filesystem type\n'
                    'and block size by appending ,FSTYPE,BLKSZ to this\n'
                    'argument.  ext[234], xfs, btrfs, & f2fs are supported for\n'
                    'FSTYPE.  Block size defaults to 4096 bytes if not given.\n\n'
                    'To convert an OverlayFS overlay to a Device-mapper\n'
                    'snapshot overlay, use "DM_snapshot_cow" as the FSTYPE.\n'
                    'To convert a Device-mapper overlay to OverlayFS on\n'
                    'non-vfat devices, use "dir" for FSTYPE and any SIZE > 0\n'
                    'or use the --flatten-squashfs option. To keep the previous'
                    '\noverlay size, specify NN as +0.\n ')

parser.add_argument('--refresh-only', action='store_true', default=False,
                    help='Specify replacing the squashfs.img or rootfs_img\n'
                    'of the source LiveOS instance with such files\nfrom the '
                    'new build, and resetting any overlay.\nNo new .iso '
                    'file will be produced.\n ')

parser.add_argument('--skip-refresh', action='store_true', default=False,
                    help='Specify no refreshening of source filesystems.\n ')

parser.add_argument('--skip-seclude', action='store_true', default=False,
                    help='Specify no seclusion of specific user files.\n ')

parser.add_argument('-c', '--compress-type', metavar='ARGS',
                    help='Specify the compression type for SquashFS.\nThis '
                         'will override the current compression or lack\n'
                         'thereof.  Multiple arguments should be specified '
                         'in\none string, i.e., --compress-type '
                         '"type arg1 arg2 ..."\n ')

parser.add_argument('--compress', action='store_true', default=None,
                    help='Specify compression for the filesystem image.\n'
                         'Used when overriding an uncompressed source.\n ')

parser.add_argument('--skip-compression', action='store_true', default=False,
                    help='Specify building a .iso file with an uncompressed,\n'
                         'root filesystem.\n ')

parser.add_argument('--refresh-uncompressed', action="store_true",
                    default=False,
                    help='Specify refreshing the source with an uncompressed,'
                         '\nroot filesystem.\n ')

parser.add_argument('--flatten-squashfs', action="store_true",
                    default=False,
                    help='Specify refreshing the source and building the .iso'
                         '\nwith an flattened squash root filesystem.  This \n'
                         'eliminates the intermediate LiveOS directory and is\n'
                         'suitable only for OverlayFS overlays.  If the source'
                         '\nhas a persistent Device-mapper overlay, it will be'
                         '\nconverted to an OverlayFS overlay.  If the source'
                         '\nis installed on a vfat formatted device, the\n'
                         'overlay will be replaced by an embedded ext4 file-\n'
                         'system, unless another filesystem type is specified\n'
                         'by the --overlay-size-mb [+|-]SIZE[,FSTYPE[,BLKSZ]]'
                         '\noption.\n ')

parser.add_argument('-a', '--extra-kernel-args', dest='kernelargs',
                    metavar='ARGS', default='',
                    help='Specify extra kernel arguments to include in the '
                         'new\n.iso file boot configuration.  Multiple '
                         'arguments\nshould be specified in one string, '
                         'i.e.,\n--extra-kernel-args "arg1 arg2 ..."\n ')

parser.add_argument('--extra-space-mb', type=int, default=2, metavar='SIZE',
                    help='Specify extra space in MiB to reserve for\n'
                         'unexpected staging area needs.  default = 2 MiB\n ')

parser.add_argument('--nocleanup', action='store_true', default=False,
                    help='Skip cleanup of temporary files.\n ')

parser.add_argument('--releasever', type=str, dest='releasever', 
                    default=None, 
                    help='Value to substitute for $releasever in kickstart '
                         'repo urls.\n ')

parser.add_argument('--arch', type=str, dest='arch', 
                    default=dnf.rpm.basearch(hawkey.detect_arch()), 
                    help='Computer instruction set architecture for the '
                         'final image.\n'
                         'default = dnf.rpm.basearch(hawkey.detect_arch())\n ')

parser.add_argument('-p', '--plugins', action='store_true', dest='plugins',
                    default=False, 
                    help='Use DNF plugins during image creation.\n ')

parser.add_argument('--cacheonly', action='store_true', dest='cacheonly',
                    default=False,
                    help='Work offline from cache, use together with '
                         '--dnfcache.\ndefault = False')

setup_logging(parser)

args = parser.parse_args()

print("\nSource image at '%s'" % args.liveos)


class LiveImageEditor(LiveImageCreator):
    """
    Class for editing LiveOS images.

    We need an instance of LiveImageCreator, however, we may not have a
    kickstart file and we may not need to create a new image.  We just want to
    reuse some of LiveImageCreator's methods on an existing LiveOS image.
    """
    def __init__(self, name, docleanup=True):
        """Initialize a LiveImageEditor instance.

        Create a dummy instance of LiveImageCreator.
        Initialize some custom bindings.
        """
        self.name = name
        """Should not contain '/' characters."""

        self.tmpdir = "/var/tmp"
        """The directory in which all temporary files will be created."""

        self.clone = False
        """Signals, when True, to copy a home.img filesystem if present."""

        self._overlay = None
        """Signals the existence and filepath of an filesystem overlay file or
        directory for merging in base_on()."""

        self.ovltype = None
        """Specifies the type of overlay ('dir', or 'temp')."""

        self.ovl_fstype = None
        """Specifies the type of filesystem in the overlay."""

        self.includes = []
        """A string of file or directory paths to copy to the .iso file in
        _copy_src_root().  Include multiple files as "file1, file2, ..."
        (The paths are referenced relative to the source mount directory,
        either /mnt/live/ or /run/initramfs/live/ for the running LiveOS, or
        /TMPDIR/editliveos-<random>/<srcmntdir>/ for an attached or .iso file.
         So ../../../<mount point>/INCLUDE or
        ../../../../<mount point>/INCLUDE may be used, respectively, to
        include files from other branches of the active hierarchy.)"""

        self.excludes = []
        """A string of filename patterns to exclude from the copy of the outer
        device filesystem.  See _copy_src_root().  Denote multiple patterns as
        "pattern1, pattern2, ..."  Default is all content outside of the LiveOS
        and iso/syslinux directories."""

        self.secludes = []
        """A string of file or directory paths to seclude from the built .iso
        file but maintain (restore to) in the refreshed filesystems."""

        self.releasefile = []
        """A string of file or directory paths to include in _brand().  This
        variable is later reused to hold the modified release name."""

        self.builder = os.getenv('USERNAME')
        """The name of the Remix builder for _branding.
        Default = os.getenv('USERNAME')"""

        self.compress_args = None
        """mksquashfs compressor to use. Use 'None' to force reading of the
        source image, or enter --compress-type "arg s" to override the
        current compression or lack thereof. Compression type options vary with
        the version of the kernel and SquashFS used. Multiple arguments should
        be specified in one string, i.e., --compress-type "type arg1 arg2 ...".
        """

        self._isofstype = "iso9660"

        self.root_fstype = None

        self.root_blksz = None

        self._ImageCreator__builddir = None
        """The staging directory for the build contents and mount points."""

        self._ImageCreator_outdir = None
        """Directory where the final .iso file gets written."""

        self._ImageCreator__bindmounts = []

        self._LoopImageCreator__fslabel = None
        self._LoopImageCreator__instloop = None
        self._LoopImageCreator__fstype = None
        self._LoopImageCreator__image_size = None

        self._LiveImageCreatorBase__isodir = None
        """Directory where the .iso contents are staged."""

        self.ks = None
        """Optional kickstart file as a recipe for editing the image."""

        self._ImageCreator__selinux_mountpoint = '/sys/fs/selinux'
        with open('/proc/self/mountinfo', 'r') as f:
            for line in f.readlines():
                fields = line.split()
                if fields[-2] == 'selinuxfs':
                    self._ImageCreator__selinux_mountpoint = fields[4]
                    break

        self.docleanup = docleanup
        """Flag signalling removal of temporary image files."""

        self.is_squashed = None
        """Flag indicating presence of a source LiveOS SquashFS."""

        self.liveossrc = None
        """Mount object for the source device."""

        self.liveosmnt = None
        """Mount object for the source LiveOS image."""

        self.srcmntdir = None
        """Mount point for the source LiveOS image partition."""

        self.newhomemnt = None
        """Mount object for a cloned home.img filesystem."""

        self.EncHomeReq = None
        """Option to specify LUKS encryption on the home.img filesystem.
        If it is not specified on the commandline, it will default to the
        status of home.img at program launch."""

        self.seclude_tar = []
        """File names for store of files secluded from the new build image."""

        self.dm_dup = None
        """Device-mapper source filesystem device duplicate."""

    # properties
    def __get_image(self):
        if self._LoopImageCreator__imagedir is None:
            self.__ensure_builddir()
            self._LoopImageCreator__imagedir = \
                tempfile.mkdtemp(dir=os.path.abspath(self.tmpdir),
                                 prefix=self.name + '-')
        rtn = self._LoopImageCreator__imagedir + '/' + self.rootfs_img
        return rtn
    _image = property(__get_image)
    """The location of the new filesystem image file."""

    def __ensure_builddir(self):
        if not self._ImageCreator__builddir is None:
            return

        try:
            self._ImageCreator__builddir = tempfile.mkdtemp(
                dir=os.path.abspath(self.tmpdir), prefix='editliveos-')
        except OSError as msg:
            raise CreatorError("Failed create build directory in %s: %s" %
                               (self.tmpdir, msg))

    def _run_script(self, script):

        (fd, path) = tempfile.mkstemp(prefix='script-',
                                      dir=self._instroot + '/tmp')

        logging.debug("copying script to install root: %s" % path)
        shutil.copy(os.path.abspath(script), path)
        os.close(fd)

        os.chmod(path, 0o700)

        script = "/tmp/" + os.path.basename(path)

        try:
            subprocess.call([script], preexec_fn=self._chroot)
        except OSError as e:
            raise CreatorError("Failed to execute script %s, %s" % (script, e))
        finally:
            os.unlink(path)


    def _pre_mount(self, base_on, rootfsimg=None, overlay=None):
        """Prepare for mounting the existing filesystem.

        base_on --  the <LiveOS_source> a LiveOS.iso file, an attached LiveOS
                    device, such as, /dev/sdd1 (or similar), the path to a
                    directory or mount point containing the LiveOS and iso/
                    syslinux folders, or "live" for a currently running LiveOS
                    image.
        rootfsimg - An optional <file path> to the root filesystem image
                    relative to the mount point for the <LiveOS_source>.
        overlay   - An optional [<devspec>:]<path> to the overlay device and
                    overlay file or directory.
        """
        if not base_on:
            raise CreatorError("No base LiveOS image specified.")

        self.__ensure_builddir()
        builddir = self._ImageCreator__builddir
        self._ImageCreator_instroot = os.path.join(builddir, 'install_root')
        self._LoopImageCreator__imagedir = os.path.join(builddir, 'ex')
        self._LiveImageCreatorBase__isodir = os.path.join(builddir, 'iso')
        self._ImageCreator_outdir = os.path.join(builddir, 'out')

        makedirs(self._ImageCreator_instroot)
        makedirs(self._LoopImageCreator__imagedir)
        makedirs(self._ImageCreator_outdir)
        makedirs('/run/media')
        self.mntdir = tempfile.mkdtemp(dir='/run/media')

        ops = ''
        f = ''  # variable & flag for iso-scan/filename booted host.
        self.ovl_size = 0
        if self.src_type == 'live':
            src = os.path.join('/run', 'initramfs', 'isoscandev')
                # (^ Requires dracut verion 051 or greater.)
            if os.path.exists(src):
                base_on = os.path.realpath(os.readlink(src))
                src = os.path.join('/proc', 'cmdline')
                with open(src, 'r') as f:
                    src = f.read()
                f = src[src.find('iso-scan/filename=')+19:src.find(' root=')]
                self.isosrcmnt = DiskMount(RawDisk(None, base_on),
                              tempfile.mkdtemp(dir=self.mntdir, prefix='dev-'),
                              dirmode=0o700)
                self.isosrcmnt.mount()
                base_on = os.path.join(self.isosrcmnt.mountdir, f)
                if not os.path.exists(base_on):
                    raise CreatorError("Failed to find '%s'." % base_on)
                self.ovltype = 'tempdir'
                self.skip_refresh = True
                self.liveosmnt = self.new_liveos_mount(base_on, '/',
                        '/run/overlayfs', self.mntdir, ops='ro', dirmode=0o700)
                self.srcmntdir = self.srcdir
            if not f:
                base_on = os.path.dirname(losetup('-nO BACK-FILE', '/dev/loop0'))
                self.srcmntdir = '/run/initramfs/live'
            fsroot = losm = '/'
            if not f:
                self.srcdir = base_on
            src = findmnt('-nro SOURCE,OPTIONS', '/').split()
            if src[0] == 'LiveOS_rootfs':
                if ':' in src[1]:
                    o = src[1].split(':')
                    base_mp = o[1][0:o[1].find(',upperdir=')]
                else:
                    base_mp = src[1][src[1].find(
                              'lowerdir=')+9:src[1].find(',upperdir=')]
                    self._overlay = src[1][src[1].find(
                                    'upperdir=')+9:src[1].find(',workdir=')]
                    if not f and os.path.islink(self._overlay):
                        self.ovltype = 'dir'
                        self._overlay = os.path.realpath(self._overlay)
                        self.overlayloop = findmnt('-no SOURCE', self._overlay)
                        if self.overlayloop:
                            self.ovltype = 'OFS'
                            self.srcdir = os.path.join(self.srcmntdir,
                                       os.path.dirname(losetup('-nO BACK-FILE',
                                          self.overlayloop)).lstrip(os.sep))
                    elif not os.path.islink(self._overlay):
                        self.ovltype = 'tempdir'
                    self.imgloop = findmnt('-no SOURCE', base_mp)
            else:
                self.liveosdir = base_on
                self.id_dm_loops('live-rw')
                if self.overlayloop:
                    self.ovltype = 'DM_snapshot_cow'
                else:
                    self.ovltype = overlay = 'DM_linear'
            if not f:
                self.liveossrc = None
            if not f and self.ovltype not in ('DM_linear', 'tempdir', 'dir'):
                self._overlay = find_overlay(base_on)
                if self._overlay:
                    if os.path.isdir(self._overlay):
                        self.ovltype = 'dir'
                    elif not stat.S_ISBLK(os.stat(self._overlay).st_mode):
                        self.overlayloop = losetup('-nO NAME -j', self._overlay)
                        self.ovltype = lsblk('-ndo FSTYPE', self.overlayloop)
                else:
                    self.ovltype = 'tempdir'
        else:
            self.liveosmnt = self.new_liveos_mount(base_on, rootfsimg, overlay,
                                          self.mntdir, ops='ro', dirmode=0o700)
            fsroot = self.liveosmnt.mountdir
            self.srcdir = self.liveosmnt.srcdir
        if os.stat(self.tmpdir).st_dev == os.stat(self.srcdir).st_dev:
            # If tmpdir dev is the same as the source dev, use the
            # mount point as base_on to avoid a separate mounting,
            # which would prevent refreshing with a move operation.
            base_on = findmnt('-no TARGET -T', self.tmpdir)
        if self.src_type in ('blk', 'dir'):
            # Create 'rw' in case the overlay is uninitialized or filesystems
            # need recovery.
            self.liveosmnt._LiveImageMount__create(ops='rw', dirmode=0o700)
            if isinstance(self.liveosmnt.livemount, OverlayFSMount):
                self.src_type = 'OFS'
                if self.liveosmnt.cowloop:
                    self.src_type = 'embedded-OFS'
                # src_type dir with no overlay also passes here.
            if self.liveosmnt.ovltype in ('', 'temp'):
                self._overlay = self.liveosmnt.cowloop.lofile

            if self.liveosmnt.ovltype in ('', 'temp', 'DM_snapshot_cow'):
                try:
                    self.liveosmnt.dm_target.create('rw')
                except MountError as e:
                    raise CreatorError("Failed to create '%s' : %s" %
                    (self.liveosmnt.dm_target.DeviceMapperTarget__name, e))
                else:
                    self._fsck_img(self.liveosmnt.dm_target.device,
                                   self.liveosmnt.imgloop.fstype)
                    self.liveosmnt.dm_target.remove()
            elif self.liveosmnt.ovltype != 'dir':
                self._fsck_img(self.liveosmnt.overlay, self.liveosmnt.ovltype)

            if self.liveosmnt.homemnt:
                if self.liveosmnt.EncHome:
                    if self.EncHomeReq is None:
                        self.EncHomeReq = True
                    self.liveosmnt.EncHome.create()
                    tgt = self.liveosmnt.EncHome.device
                else:
                    tgt = self.liveosmnt.homemnt.diskmount.disk.lofile
                self._fsck_img(tgt, self.liveosmnt.homemnt.fstype)
            ops = 'ro'
        if self.src_type != 'live':
            try:
                # Read-only devices speed image copying.
                self.liveosmnt.mount(ops=ops, dirmode=0o400)
            except MountError as e:
                raise CreatorError("Failed to mount '%s' : %s" %
                                   (base_on, e))
            losm = self.liveosmnt
            if losm.squashmnt:
                self.is_squashed = True
                self.squashloop = losm.squashloop.device
            self.rootfs_img = os.path.basename(losm.imgloop.lofile)
            if losm.ovltype not in ('', 'temp', 'DM_linear'):
                if losm.ovltype == 'dir':
                    self._overlay = losm.overlay
                elif losm.ovltype in ('', 'DM_snapshot_cow'):
                    self._overlay = losm.cowloop.lofile
                    self.overlayloop = losm.cowloop.device
                else:
                    self._overlay = losm.livemount.cowloop.lofile
                    self.overlayloop = losm.livemount.cowloop.device
                    losm.imgloop.create()
            self.ovltype = self.liveosmnt.ovltype
            self.srcmntdir = findmnt('-no TARGET -T', losm.srcdir)
            self.imgloop = losm.imgloop.device

        if self.src_type == 'live':
            if not self.imgloop:
                self.imgloop = os.path.realpath('/dev/live-base')
            for i in ('0', '1'):
                if lsblk(' -ndo FSTYPE /dev/loop' + i) == 'squashfs':
                    self.is_squashed = True
                    self.squashloop = '/dev/loop' + i
                    break
            if lsblk(' -ndo FSTYPE ' + self.imgloop) == 'squashfs':
                self.is_squashed = True
                self.squashloop = self.imgloop
            if 'linear' in get_dm_table('live-rw'):
                self._overlay = None
                self.ovl_size = 0
            elif not f and not self._overlay and self.liveosmnt:
                self._overlay = self.liveosmnt.overlay.device
            if self.ovltype == 'dir':
                self.overlaydevice = findmnt('-no SOURCE -T',
                                        '/run/initramfs/overlayfs')
            img = self.imgloop
            self.rootfs_img = os.path.basename(losetup('-nO BACK-FILE', img))
            if f:
                self.liveosdir = os.path.join(self.srcmntdir, 'LiveOS')
            else:
                self.liveosdir = self.srcdir
            if src[0] == '/dev/mapper/live-rw':
                self._LoopImageCreator__fstype = findmnt('-no FSTYPE', src[0])
            else:
                self._LoopImageCreator__fstype = findmnt('-no FSTYPE', img)
        else:
            self.liveosdir = self.liveosmnt.liveosdir
            img = losm.imgloop.device
            self._LoopImageCreator__fstype = losm.imgloop.fstype

        self._LoopImageCreator__fslabel = lsblk('-ndo LABEL', img)
        self._LoopImageCreator__blocksize = os.stat(img).st_blksize
        if self.root_blksz:
            self._LoopImageCreator__blocksize = self.root_blksz
        if self._LoopImageCreator__image_size is None:
            self._LoopImageCreator__image_size = int(get_blockdev_size(img))
            if self._LoopImageCreator__fstype == 'squashfs':
                self._LoopImageCreator__fstype = 'ext4'
                # Estimate root filesystem size suitable for native image.
                self._LoopImageCreator__image_size *= 4
        if self.root_fstype == self._LoopImageCreator__fstype:
            self.root_fstype = None
        if self.root_fstype:
            self._LoopImageCreator__fstype = self.root_fstype
        if not os.path.exists(self.liveosdir):
            raise CreatorError("No LiveOS directory on %s" % base_on)
        self.bootfolder = os.path.join(self.srcdir, 'syslinux')
        self.imagesdir = os.path.join(self.srcdir, 'images')
        if not os.path.exists(self.imagesdir):
            self.imagesdir = os.path.join(self.srcmntdir, 'images')
            self.bootfolder = os.path.join(self.srcmntdir, 'syslinux')
        if not os.path.exists(self.bootfolder):
            self.bootfolder = os.path.join(self.srcdir, 'isolinux')

        self.home_img = os.path.join(self.liveosdir, 'home.img')
        self.isohome_img = os.path.join(self._LiveImageCreatorBase__isodir,
                                        'LiveOS', 'home.img')

        if self.rootfs_size == '+0':
            self.rootfs_size = ''
        elif self.rootfs_size:
            if self.rootfs_size.startswith('+') or int(self.rootfs_size) < 0:
                self.rootfs_size = (self._LoopImageCreator__image_size +
                                        int(self.rootfs_size) * 1024 ** 3)
            else:
                self.rootfs_size = (int(self.rootfs_size) * 1024 ** 3)
            self._LoopImageCreator__image_size = self.rootfs_size

        if self._space_to_proceed(self.srcmntdir, fsroot):
            if self.home_size_mb and self.home_size_mb > 0:
                self.shift_home()
            if not self.refresh_only:
                print('Copying the included folders and files.')
                self._copy_src_root(self.srcmntdir)
        else:
            raise CreatorError('''Insufficient space to stage a new build.
            Exiting...\n''')

        if self.src_type in ('blk', 'dir') and losm.ovltype in ('', 'temp',
                                                           'DM_snapshot_cow'):
            self.liveosmnt.unmount()
        if (self.src_type == 'iso' and self.ovltype != 'dir'
            and not self.root_fstype):
            self.liveosmnt.cleanup()
            if self.is_squashed:
                self.liveosmnt.squashmnt.cleanup()
            LiveImageCreator._base_on(self, base_on)
            self.fslabel = self._LoopImageCreator__fslabel = self.name
            self._LoopImageCreator__instloop = ExtDiskMount(
                    ExistingSparseLoopbackDisk(self._image,
                                           self._LoopImageCreator__image_size),
                    self._ImageCreator_instroot,
                    self._LoopImageCreator__fstype,
                    self._LoopImageCreator__blocksize,
                    self.fslabel)
        else:
            self._LoopImageCreator__fslabel = self.name
            # Copy base_on into rootfs_img at this point.
            self._base_on(losm)

        if self.rootfs_size:
            self._LoopImageCreator__instloop.disk._size = self.rootfs_size

            print('Setting root image size to %s bytes.' % self.rootfs_size)
            self._LoopImageCreator__instloop._ExtDiskMount__resize_filesystem(
                                                          self.rootfs_size)
            if self.src_type != 'iso':
                self._LoopImageCreator__instloop.cleanup()


    def new_liveos_mount(self, base_on, rootfsimg=None, overlay=None,
                         mntdir=None, ops='', dirmode=None):
        """
        Initialize a new mount object for the LiveOS image of an .iso,
        attached, or refreshed device.  Optionally, pass a rootfsimg or
        overlay reference.
        """
        if os.path.isfile(base_on):
            self.liveossrc = DiskMount(LoopbackDisk(base_on, None, '-r'),
                             tempfile.mkdtemp(dir=mntdir, prefix='iso-'))
            self.srcdir = self.liveossrc.mountdir
            ops = 'ro'
        elif os.path.isdir(base_on):
            self.liveossrc = None
            self.srcdir = base_on
        elif self.src_type == 'blk' and not self.liveossrc:
            if lsblk('-nrdo TYPE', base_on) == 'part':
                self.liveossrc = DiskMount(RawDisk(None, base_on),
                                 tempfile.mkdtemp(dir=mntdir, prefix='dev-'),
                                 dirmode=dirmode)
            else:
                raise CreatorError("%s' is not a partition.  Please check "
                                   "your inputs.  Exiting..." % base_on)
        if self.liveossrc:
            self.liveossrc.mount(dirmode=0o400)
            self.liveossrc.rmdir = True
            self.srcdir = self.liveossrc.mountdir

        liveosmnt = LiveImageMount(self.srcdir, tempfile.mkdtemp('-', 'LIM-',
                         mntdir), rootfsimg, overlay, ops=ops, dirmode=dirmode)
        if not liveosmnt:
            raise CreatorError("Unable to create LiveImageMount with '%s',"
                               "  Exiting..." % base_on)
        return liveosmnt


    def _space_to_proceed(self, srcmntdir, fsroot):
        """
        Provide estimated size requirements and availability of staging
        resources for the build.
        """
        # Determine compression type.
        if self.is_squashed:
            squash_img = losetup('-nO BACK-FILE', self.squashloop)
            img = squash_img
            required = int(get_blockdev_size(self.squashloop))
        else:
            self.compress_args = 'gzip'
        # 'self.compress_args = None' will force reading it from base_on.
        if self.compress_args is None:
            self.compress_args = squashfs_compression_type(img)
            if self.compress_args in ('undetermined', 'unknown'):
                # 'gzip' for compatibility with older versions.
                self.compress_args = 'gzip'

        du_args = ['du', '-csxb', '--files0-from=-']

        ignore_list = [self.rootfs_img, 'squashfs.img',
                       'home.img', 'swap.img', 'overlay-*']
        if self.clone and not int(self.home_size_mb) >= 0:
            ignore_list.remove('home.img')
        [du_args.append('--exclude=%s' % fn) for fn in ignore_list]
        if not self.skip_seclude:
            [du_args.append('--exclude=%s' % fn) for fn in self.secludes]

        # Remove duplicate files to reduce .iso size.
        for dn in ('BOOT', 'boot'):
            for fn in ('vmlinuz', 'vmlinuz0', 'initrd.img', 'initrd0.img'):
                path = os.path.join('EFI', dn, fn)
                f_path = os.path.join(srcmntdir, path)
                if (os.path.exists(f_path) and not os.path.islink(f_path)):
                    self.excludes.append(path)

        if findmnt('-no FSTYPE -T', fsroot) == 'overlay':
            rootfs_used = int(rcall(['df', '-B1', '--output=used', fsroot],
                                    raise_err=False)[0].split()[-1])
        else:
            rootfs_used = int(rcall(du_args, fsroot,
                                    raise_err=False)[0].split()[-2])
        if rootfs_used > required:
            required = rootfs_used

        self.home_used = int(rcall(du_args, os.path.join(fsroot, 'home'),
                              raise_err=False)[0].split()[-2])
        homesize = self.home_img_size = 0
        if os.path.exists(self.home_img):
            homesize = self.home_img_size = os.stat(self.home_img).st_size
        overlaysize = None
        if self._overlay and self.src_type != 'iso':
            if self.ovltype in ('dir', 'tempdir', None):
                 overlaysize = int(rcall(['du', '-csxb', self._overlay],
                                         raise_err=False)[0].split()[-2])
            else:
                if os.path.isfile(self._overlay):
                    if self.liveosmnt:
                        self.overlayloop = self.liveosmnt.cowloop.device
                    img = self.overlayloop
                else:
                    img = self._overlay
                overlaysize = int(get_blockdev_size(img))
            self.ovl_size = overlaysize

        if self.home_size_mb is not None:
            if self.home_size_mb.startswith('+') and homesize > 0:
                self.home_size_mb = self.home_img_size + (int(
                                           self.home_size_mb) * 1024 ** 2)
            else:
                self.home_size_mb = int(self.home_size_mb) * 1024 ** 2
            if self.home_size_mb < 0:
                self.home_size_mb = self.home_img_size + self.home_size_mb
            if self.home_size_mb < self.home_used and self.home_size_mb != 0:
                self.home_size_mb = self.home_used + self.home_used // 8
            if self.src_type == 'live' and self.home_size_mb > 0:
                # space for staging new home.img filesystem
                required += self.home_size_mb
            homesize = self.home_size_mb
            if self.home_size_mb <= 0:
                if os.path.exists(self.home_img):
                    required -= self.home_used // 2
                else:
                    self.home_size_mb = None
            if self.home_size_mb == self.home_img_size and \
                                                      self.EncHomeReq is None:
                self.home_size_mb = None
        elif self.EncHomeReq is not None:
            self.home_size_mb = self.home_img_size

        if self.overlay_size_mb:
            if self.overlay_size_mb.startswith('+'):
                self.overlay_size_mb = self.ovl_size + (int(
                                       self.overlay_size_mb) * 1024 ** 2)
            else:
                self.overlay_size_mb = int(
                                        self.overlay_size_mb) * 1024 ** 2
            if not self.refresh_only and self._overlay and os.stat(
                self._overlay).st_dev == os.stat(self.output).st_dev:
                # if overlay is on staging device
                if self.overlay_size_mb > 0:
                    required += self.overlay_size_mb - self.ovl_size
                else:
                    required += self.overlay_size_mb
            if self.overlay_size_mb < 0:
                self.overlay_size_mb = (self.ovl_size +
                                        self.overlay_size_mb)
            overlaysize = self.overlay_size_mb

        if self.skip_compression:
            # Space on iso image
            required += rootfs_used
        else:
            # assuming a 2:1 compression ratio, at most
            required += rootfs_used // 2

        if self.src_type == 'iso':
            required += 64 * 1024 ** 2

        available = int(disc_free_info(self.tmpdir, '-TB1')[4]) # * 2
        # Inflate available space to cover over estimated space requirements.^

        if not self.refresh_only and os.stat(self.tmpdir).st_dev == os.stat(
                                                           self.output).st_dev:
            required += self._LoopImageCreator__image_size
        else:
            out_available = int(disc_free_info(self.output, '-TB1')[4])
            if self._LoopImageCreator__image_size > available:
                if self._LoopImageCreator__image_size < out_available:
                    print('''
                    There is insufficient space on %s to build the image.
                    %s will be tried as a temporary directory.''' % (
                        self.tmpdir, self.output))
                    self.tmpdir = self.output
                    required += self._LoopImageCreator__image_size
                else:
                    needed = (self._LoopImageCreator__image_size -
                              available) / (1024 ** 2)
                    print('''
                    There is insufficient space on %s to build the image.
                    %d MiB of additional space is needed.''' % (self.tmpdir,
                                                                needed))
                    if self.liveosmnt:
                        self.liveosmnt.cleanup()
                    return False

        if os.path.basename(self._ImageCreator__builddir) in os.listdir(
                                                                    srcmntdir):
            self.excludes.append(os.path.basename(
                                 self._ImageCreator__builddir))
        [du_args.extend(['--exclude=%s' % os.path.join(srcmntdir,
                         fn.lstrip(os.sep))]) for fn in self.excludes]

        files0from = ''.join((srcmntdir, '\0'))
        if self.exclude_all:
            files0from = os.path.join(self.srcdir)
            files0from = '\0'.join((files0from, os.path.join(srcmntdir,
                                    os.path.basename(self.bootfolder))))
        elif not self.refresh_only:
            for i, fn in enumerate(self.includes):
                src = os.path.join(srcmntdir, fn.lstrip(os.sep))
                files0from = '\0'.join((files0from, src))
                self.includes[i] = src
        if self.refresh_only:
            tobe_copied = 0
        else:
            tobe_copied = int(rcall(du_args, files0from,
                                    raise_err=False)[0].split()[-2])
        # One staged copy and another in iso9660 file.
        required += tobe_copied * 2
        # amount for uncertain overhead
        required += self.extra_space

        self.fmt = '{0:20,}'
        if float(sys.version[:3]) < 2.7:
            self.fmt = '{0:20}'
        print(''.join(('\n ', self.fmt,
                      ' extra bytes set to be copied from the sources.')
                     ).format(tobe_copied),)
        print(''.join((' ', self.fmt,
                   ' bytes used in the LiveOS filesystem in {1:s}.\n')).format(
                       rootfs_used, self.src))
        if homesize > 0:
            print(''.join(('(', self.fmt,
                        ') bytes in home.img filesystem.')).format(homesize))
        if overlaysize:
            print(''.join(('(', self.fmt,
                           ') bytes in the overlay.\n')).format(
                           overlaysize))
        print(''.join((' ', self.fmt,
                     ' total bytes, root filesystem image size.\n')).format(
                       self._LoopImageCreator__image_size))
        print(''.join((' ' , self.fmt,
               ' bytes are estimated to be required for staging this build.\n')
                     ).format(required),)
        print(''.join((' ', self.fmt, ' bytes are available on'
                     )).format(available),
                       '{0:s} for staging the build.'.format(self.tmpdir))
        if available <= required:
            needed = (required - available) // (1024 ** 2)
            print('''
            There is insufficient space to build a remix on %s.
            %d MiB of additional space is needed.''' % (self.tmpdir, needed))
            if self.liveosmnt:
                self.liveosmnt.cleanup()
            return False
        elif self._LoopImageCreator__image_size < rootfs_used:
            need = round((rootfs_used - self._LoopImageCreator__image_size) /
                           (1024.0 * 1024 ** 2), 2)
            request = int(self.rootfs_size or 0) / (1024 ** 3)
            print('''
            The root filesystem uses %4.2f GiB more space than the
                %s GiB requested with --rootfs-size-gb.\n''' % (need, request))
            if self.liveosmnt:
                self.liveosmnt.cleanup()
            return False
        return True


    def shift_home(self):
        """Build and resize new home.img filesystem."""

        def mirror_home(source, ops=None):
            self.liveosmnt.homemnt.unmount()
            mt = config_mirror_targets(source, self._ImageCreator__builddir,
                                       ops=ops)
            self.newhomemnt = mt[3][0]
            mt[3][0].mountdir = os.path.join(self._instroot, 'home')
            print('Copying home.img filesystem.')
            mirror_fs(mt[0], mt[1], mt[2], mt[3], mt[4])
            call(['dmsetup', 'remove', mt[4]])

        if self.EncHomeReq and not self.liveosmnt.EncHome:
            if self.liveosmnt.homemnt:
                self.liveosmnt.homemnt.unmount()
                mirror_home(self.home_img, ops='encrypt')

        elif self.EncHomeReq and self.liveosmnt.EncHome:
            self.newhomemnt = self.liveosmnt.homemnt

        elif self.EncHomeReq is False and self.liveosmnt.EncHome:
            self.liveosmnt.homemnt.unmount()
            mirror_home(self.liveosmnt.EncHome.device)
            self.liveosmnt.EncHome.cleanup()
            self.newhomemnt.disk.cleanup()

        else:
            self.newhomemnt = ExtDiskMount(ExistingSparseLoopbackDisk(
                                           self.home_img, self.home_size_mb),
                              os.path.join(self._instroot, 'home'),
                              self.home_fstype, self.home_blksz,
                              'home', dirmode=0o700)

        if os.path.exists(self.home_img):
            ops = []
            if self.src_type == 'live':
                self.newhome_img = tempfile.mkstemp(suffix='.img', prefix='h-',
                                   dir=self._LoopImageCreator__imagedir)[1]
                self.newhomemnt.disk.lofile = self.newhome_img
                print('Copying home.img')
                shutil.copy2(self.home_img, self.newhome_img)
                self._fsck_img(self.newhome_img, self.newhomemnt.fstype)
            else:
                self.liveosmnt.homemnt.cleanup()
                if self.newhomemnt.disk.lofile != self.home_img:
                    self.newhome_img = self.newhomemnt.disk.lofile

            if self.home_size_mb != self.home_img_size:
                print('  Resizing home.img')
                if self.EncHomeReq:
                    self.newhomemnt.disk._CryptoLUKSDevice__resize_filesystem(
                                                    self.home_size_mb, ops=ops)
                elif self.newhomemnt._ExtDiskMount__resize_filesystem(
                              self.home_size_mb, ops=ops) < self.home_img_size:
                    self.newhomemnt.disk.truncate(self.home_size_mb)
        elif not self.EncHomeReq:
            self.newhome_img = tempfile.mkstemp(suffix='.img', prefix='home-',
                               dir=self._LoopImageCreator__imagedir)[1]
            self.newhomemnt.disk = SparseLoopbackDisk(self.newhome_img,
                                                      self.home_size_mb)
            self.newhomemnt.mountdir = os.path.join(self.mntdir, 'home')
            os.unlink(self.newhome_img)


    def _copy_src_root(self, srcmntdir):
        """Copy root content of the base LiveOS source to ISOdir."""

        isodir = self._LiveImageCreatorBase__isodir

        ignore_list = [self.rootfs_img, 'squashfs.img', 'ovlwork', 'osmin.img',
         'home.img', 'swap.img', 'overlay-*', 'images', 'syslinux', 'isolinux']
        if self.clone and not self.home_size_mb and self.EncHomeReq is None:
            ignore_list.remove('home.img')

        [ignore_list.extend([fn]) for fn in self.excludes]

        if self.excludes:
            print('''
            \rFolders & files potentially excluded from the source root copy:
            \r  %s''' % self.excludes)

        src, dst = srcmntdir, isodir
        if self.exclude_all:
            src = self.liveosdir
            dst = os.path.join(isodir, 'LiveOS')
        if srcmntdir != self.srcdir:
            dst = os.path.join(isodir, 'LiveOS')

        shutil.copytree(src, dst, symlinks=True,
                        ignore=shutil.ignore_patterns(*ignore_list))

        for src in (self.bootfolder, self.imagesdir):
            copypaths([src], isodir)

        for src in ('LICENSE', 'Fedora-Legal-README.txt', 'EFI'):
            src = os.path.join(self.srcmntdir, src)
            if os.path.exists(src):
                copypaths([src], isodir)

        copypaths(self.includes, isodir, ignore_list, message='Additional '
                  'folders and files to be included in the new image:')

        installedpath = os.path.join(isodir, 'syslinux')
        if os.path.exists(installedpath):
            os.rename(installedpath, os.path.join(isodir, 'isolinux'))

        # Build symlinks to reduce .iso file size.
        #  src should be EFI directory from above.
        for dn in ('BOOT', 'boot'):
            for fn in ('vmlinuz', 'vmlinuz0', 'initrd.img', 'initrd0.img'):
                if os.path.exists(os.path.join(src, dn, fn)):
                    link = os.path.join(isodir, 'EFI', dn, fn)
                    if os.path.exists(link):
                        os.unlink(link)
                        os.symlink(os.path.join('../..', os.path.basename(
                                                   self.bootfolder), fn), link)

        if os.path.exists(self.isohome_img):
            print('Checking home.img')
            self._fsck_img(self.isohome_img, self.liveosmnt.homemnt.fstype)


    def id_dm_loops(self, dm_dev):
        """Identify device-mapper loop devices."""

        dm_table_list = get_dm_table(dm_dev).split()
        self.imgsize = dm_table_list[1]
        self.imgloop = '/dev/loop' + dm_table_list[3].split(':')[1]
        if dm_table_list[2] == 'snapshot':
            self.overlayloop = '/dev/loop' + dm_table_list[4].split(':')[1]
        elif dm_table_list[2] == 'linear':
            self.overlayloop = None
        self.osminloop = None
        if self.src_type == 'live':
            dm_table_list = get_dm_table('live-osimg-min').split()
            if dm_table_list:
                self.osminloop = '/dev/loop' + dm_table_list[4].split(':')[1]
                self.osminsquashloop = losetup('-nO NAME -j', '/osmin.img')


    def _base_on(self, losm):
        """Clone the running or a LiveOS image as the basis for the new image.
        """
        if isinstance(losm, LiveImageMount):
            if isinstance(losm.livemount, OverlayFSMount):
                losm = losm.mountdir

        if isinstance(losm, LiveImageMount) and not self.root_fstype:
            dm_dev = losm.dm_target.DeviceMapperTarget__name
            self.id_dm_loops(dm_dev)
            if self.osminloop:
                call(self.dmsetup_cmd + ['remove', 'live-osimg-min'])
                call(['losetup', '-d', self.osminloop])
                call(['losetup', '-d', self.osminsquashloop])

            mt = config_mirror_targets(dm_dev, self._ImageCreator__builddir)
            mt[3][0].disk.lofile = self._image
            mt[3][0].mountdir = self._instroot
            self._LoopImageCreator__instloop = mt[3][0]

            print('Copying LiveOS root filesystem.')
            mirror_fs(mt[0], mt[1], mt[2], mt[3], mt[4])
            call(['dmsetup', 'remove', mt[4]])

        else:
            if isinstance(losm, LiveImageMount):
                if not losm.mounted:
                    losm.mount()
                losm = losm.mountdir
            losfs = os.path.join(losm, '.')

            self._LoopImageCreator__instloop = ExtDiskMount(SparseLoopbackDisk(
                                           self._image,
                                           self._LoopImageCreator__image_size),
                                           self._ImageCreator_instroot,
                                           self._LoopImageCreator__fstype,
                                           self._blocksize,
                                           self._LoopImageCreator__fslabel)
            self._LoopImageCreator__instloop.mount()

            print('Copying LiveOS root filesystem.  Please wait...')
            rc = call(['cp', '-ax', losfs,
                       self._LoopImageCreator__instloop.mountdir])
            if rc != 0:
                raise CreatorError("Failed to copy root filesystem '%s' : %s" %
                            (losfs, self._LoopImageCreator__instloop.mountdir))
            if self.liveosmnt:
                self.liveosmnt.unmount()


    def mount(self, cachedir=None):
        """Mount the source filesystem.

        We have to override mount because we many not be creating a new install
        root, nor do we need to setup the filesystem, i.e., makedirs (/etc/,
        /boot, ...), and we may not want to overwrite fstab or create selinuxfs.

        We also need to get some info about the image before we can mount it.

        cachedir -- a directory in which to store a DNF or 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.
        """
        try:
            DiskMount.mount(self._LoopImageCreator__instloop)
        except MountError as e:
            raise CreatorError("Failed to loop mount '%s' : %s" %
                               (self._image, e))

        os.chmod(self._ImageCreator_instroot, 0o555)
        litd = os.path.join(self._LiveImageCreatorBase__isodir, 'LiveOS',
                            'livecd-iso-to-disk')
        if os.path.exists(litd):
            os.chmod(litd, 0o555)

        homedir = os.path.join(self._ImageCreator_instroot, 'home')
        if self.home_size_mb and self.home_size_mb >= 0:
            if self.home_size_mb == 0:
                if self.src_type == 'live':
                    homeroot = '/home'
                else:
                    if self.liveosmnt.EncHome:
                        self.liveosmnt.EncHome.cleanup()
                        tmphome_mnt = DiskMount(CryptoLUKSDevice('EncHome',
                                      self.home_img, ops='ro'),
                                      tempfile.mkdtemp(dir=self.mntdir),
                                      ops='ro', dirmode=0o400)
                        homeroot = tmphome_mnt.mountdir
                    else:
                        tmphome_mnt = LoopbackMount(self.home_img,
                                      tempfile.mkdtemp(dir=self.mntdir),
                                      ops='ro', dirmode=0o400)
                        homeroot = tmphome_mnt.diskmount.mountdir
                    try:
                        tmphome_mnt.mount()
                    except MountError as e:
                        raise CreatorError("Failed to mount '%s' : %s" %
                                           (homeroot, e))
                print('Copying home.img contents.')
                p = os.path.join(homedir, os.path.basename(homeroot))
                call(['cp', '-a', homeroot, homedir])
                for fn in os.listdir(p):
                    shutil.move(os.path.join(p, fn), homedir)
                os.rmdir(p)
                if not os.listdir(os.path.join(homedir, 'lost+found')):
                    os.rmdir(os.path.join(homedir, 'lost+found'))
                if self.src_type != 'live':
                    tmphome_mnt.cleanup()
                os.remove(self.home_img)
                if liveosmnt:
                    self.liveosmnt.homemnt = None
            elif not os.path.exists(self.home_img):
                self.newhomemnt.mount()
                print('Copying /home contents to home.img filesystem.')
                p = os.path.join(self.newhomemnt.mountdir, 'home')
                call(['mv', homedir, self.newhomemnt.mountdir])
                for fn in os.listdir(p):
                    shutil.move(os.path.join(p, fn), self.newhomemnt.mountdir)
                os.rmdir(p)
                os.mkdir(homedir, 0o755)
                if self.EncHomeReq:
                    self.newhomemnt.unmount()
                    self.newhome_img = self.home_img
                else:
                    self.newhomemnt.cleanup()
                    print('Copying home.img to source device.')
                    shutil.copy2(self.newhome_img, self.home_img)
                self.newhomemnt.mountdir = homedir
            elif ((self.src_type == 'live' or self.EncHomeReq is not None)
                 and hasattr(self, 'newhome_img')):
                os.remove(self.home_img)
                print('Copying home.img to source device.')
                shutil.copy2(self.newhome_img, self.home_img)
                self.newhomemnt.disk.lofile = self.home_img
            else:
                self.newhomemnt.mountdir = homedir

            if hasattr(self, 'newhome_img'):
                if self.clone:
                    shutil.copy2(self.newhome_img, self.isohome_img)
                    self.newhomemnt.disk.lofile = self.isohome_img

                if self.EncHomeReq:
                    self.newhomemnt.disk.create()
                    p = self.newhomemnt.disk.device
                else:
                    LoopbackDisk.create(self.newhomemnt.disk)
                    p = self.newhomemnt.disk.lofile
                print('Checking new home.img')
                self._fsck_img(p, self.newhomemnt.fstype)
        else:
            if self.clone and os.path.exists(self.isohome_img):
                self.newhomemnt = DiskMount(ExistingSparseLoopbackDisk(
                                                           self.isohome_img,
                                                           self.home_img_size),
                                            homedir)
            if self.src_type == 'live':
                self.newhomemnt = BindChrootMount('/home', self._instroot,
                                                  None)
            elif self.liveosmnt.homemnt:
                if self.liveosmnt.EncHome:
                    self.newhomemnt = DiskMount(self.liveosmnt.EncHome, homedir)
                else:
                    self.newhomemnt = DiskMount(ExistingSparseLoopbackDisk(
                                                           self.home_img,
                                                           self.home_img_size),
                                                homedir)

        try:
            if self.newhomemnt:
                self.newhomemnt.mount()
        except MountError as e:
            raise CreatorError("Mount of '%s' on '%s' failed: %s" %
                      (self.newhomemnt.lofile, self.newhomemnt.disk.device, e))

        if self.script or self.shell:
            cachesrc = cachedir or (
                self._ImageCreator__builddir + "/repo-cache")
            makedirs(cachesrc)

            urandom = os.path.join(self._instroot, 'dev', 'urandom')
            if not os.path.exists(urandom):
                origumask = os.umask(0000)
                os.mknod(urandom, 0o666 | stat.S_IFCHR, os.makedev(1, 9))
                os.umask(origumask)

            bindmounts = [('/sys', None), ('/proc', None), ('/dev/pts', None),
                          ('/dev/shm', None), ('/run', None),
                          ('/etc/resolv.conf', None),
                          (cachesrc, '/var/cache/dnf'),
                          (cachesrc, '/var/cache/yum'),
                          (self._LiveImageCreatorBase__isodir, '/mnt/live'),
                          (self.srcmntdir, '/run/initramfs/live'),
                          ('/', '/run/hostroot')]
            if self.force_selinux:
                bindmounts += [(self._ImageCreator__selinux_mountpoint, None)]
            for (f, dest) in bindmounts:
                if os.path.exists(f):
                    self._ImageCreator__bindmounts.extend(
                        [BindChrootMount(f, self._instroot, dest)])
                else:
                    logging.warning("Skipping (%s, %s) because source doesn't "
                                 "exist." % (f, dest))
            self._do_bindmounts()
            if self.force_selinux:
                print('Setting SELinux contexts...')
                self._ImageCreator__create_selinuxfs(force=True)

        mtab = os.path.join(self._instroot, 'etc', 'mtab')
        if not os.path.islink(mtab):
            os.remove(mtab)
            os.symlink('/proc/self/mounts', mtab)


    def check_kernel_versions(self, isodir, msg):
        """
        Check that the kernel version in the root filesystem matches that
        called by the boot loader.
        """
        self.kernel = os.path.join(isodir, 'vmlinuz')
        if not os.path.exists(self.kernel):
            self.kernel = os.path.join(isodir, 'vmlinuz0')
        vm = os.path.basename(self.kernel)
        self.kernel_release = get_file_info(self.kernel)[7]
        if not self.kernels:
            self.kernels = glob.glob(os.path.join(self._instroot, 'usr',
                                             'lib', 'modules', '*', 'vmlinuz'))
            self.kernels = [os.path.basename(e) for e in [os.path.dirname(e)
                                                        for e in self.kernels]]
        self.initrd = os.path.join(isodir, 'initrd.img')
        if not os.path.exists(self.initrd):
            self.initrd = os.path.join(isodir, 'initrd0.img')
        img = os.path.basename(self.initrd)
        if msg == 'update':
            cmd = ['sort', '-rV']
            a, err, rc = rcall(cmd, '\n'.join(self.kernels))
            a = a.split('\n')[0]
            if self.kernels != self.kernels0 or self.initrd_kernel != a:
                print('Updating kernel & initial ram filesystem images...')
                rc = os.path.join(self._instroot, 'boot',
                                                   'initramfs-' + a + '.img')
                d = os.path.join(self._instroot, 'usr', 'lib', 'modules', a)
                cmd = ['dracut', rc, '--kmoddir', d, '--kver', a,
                       '--no-hostonly', '--add', 'dmsquash-live', '--force']
                if self.src_fstype == 'f2fs':
                    cmd += ['--add-drivers', 'f2fs']
                print('using this command:\n%s' % cmd)
                call(cmd)
                a = os.path.join(d, vm)
                imagedirs = [self.bootfolder,
                  os.path.join(self.imagesdir, 'pxeboot'),
                  os.path.join(self._LiveImageCreatorBase__isodir, 'isolinux'),
                  os.path.join(self._LiveImageCreatorBase__isodir, 'images',
                               'pxeboot')]
                if not os.access(imagedirs[1], os.W_OK):
                    del imagedirs[0:2]
                [shutil.copy2(rc, os.path.join(d, img)) for d in imagedirs]
                [shutil.copy2(a, os.path.join(d, vm)) for d in imagedirs]
            return

        print('Checking kernel versions in %s...' % msg)
        c = os.getcwd()
        os.chdir('/tmp')
        if os.path.exists('usr'):
            shutil.rmtree('usr')
        cmd = ['lsinitrd', self.initrd, '--unpack', '--file', os.path.join(
               'usr', 'lib', 'modules', '*', 'kernel'), '-v', '2>&1']
        a, initrd_kernel, rc = rcall(cmd)
        os.chdir(c)
        self.initrd_kernel = os.path.basename(os.path.dirname(
                                              initrd_kernel.rstrip()))
        a = rc = ''
        if self.kernel_release not in self.kernels:
            a = '''
          * This version is not among those installed in the root filesystem:
            \r%s''' % '\n'.join(self.kernels)
        if self.kernel_release != self.initrd_kernel:
            rc = '''
          * There is a mismatch between the kernel and initial ram filesystem.
            \r'''
        if a or rc:
            s = input('''\nTAKE NOTE:  The %s boot loader invokes kernel version
           '%s'
            using an initial ram filesystem compiled with kernel
           '%s'
          %s
          %s
            The kernel may have been upgraded or reside in an overlay.
            If the boot kernel version is not available in the root filesystem
            or match the initial ram filesystem version, the booted operating
            system will most likely fail.\n
            Enter 'a' to abort, or
            Enter 's' to launch a shell, or
            just press Enter to continue...
            ''' % (msg, self.kernel_release, self.initrd_kernel, a, rc))
            if s == 'a':
                raise CreatorError('''Aborting edit due to mismatching kernels.
                Exiting...''')
            elif s == 's' and 'current' in msg:
                self.shell = True
            elif s == 's':
                self.launch_shell()
                self.check_kernel_versions(isodir, msg)


    def _brand(self, isodir):
        """
        Adjust the image branding to show its variation from the original
        source by name, builder, and build date.
        """
        for f in ('isolinux.cfg', 'syslinux.cfg', 'extlinux.conf'):
            self.cfgf = os.path.join(isodir, f)
            if os.path.exists(self.cfgf): break
        # Get release name from boot configuration file.
        cmd = ['sed', '-n', '-r']
        sedscript = r'''/^\s*label\s+linux/I {n
                      s/^\s*menu\s+label\s+\^\S+\s+(.*)/\1/Ip;q}'''
        cmd.extend([sedscript, self.cfgf])
        release, err, rc = rcall(cmd)
        if not release:
            return
        self.release = release = release.strip()
        f = release.find('Remix-')
        if f > -1:
            release = release[f+6:]

        self.kernel_release = self.kernel_release.rsplit('.', 2)
        arch = self.kernel_release[-1]

        if self.name.startswith(':'):
            nametext = self.name.lstrip(':')
            if nametext:
                self.name = nametext
            else:
                self.releasefile = self.name = ''.join(('remixed-',
                                                        self.fslabel))
        else:
            nametext = ''.join((time.strftime('%d%b%Y'),
                                '-', self.builder, '-Remix-', release))
            self.name = ''.join((self.name, '-', arch, '-',
                                 time.strftime('%Y%m%d.%H%M')))
        self.fslabel = self.name

        # self.name of ':' is stripped to '', thus bypassing the renaming.
        if nametext:
            instroot = self._instroot
            # Update fedora-release message with Remix details.
            releasefiles = ['fedora-release', 'generic-release', 'issue']
            releasefiles = [os.path.join(instroot, 'etc', rf)
                           for rf in releasefiles]
            [releasefiles.extend([os.path.join(instroot, fn.lstrip(os.sep))])
                for fn in self.releasefile]
            for rfpath in releasefiles:
                if os.path.exists(rfpath):
                    try:
                        f = open(rfpath, 'r')
                        text = '\n'.join((nametext, f.read()))
                        f.close()
                        f = open(rfpath, 'w')
                        f.write(text)
                        f.flush()
                    except IOError as e:
                        raise CreatorError("Failed to open or write '%s' : %s"
                                           % (rfpath, e))
                    finally:
                        os.fsync(f.fileno())
                        f.close()
            self.releasefile = nametext

        sedscript = r'''/^\s*label\s+linux/I {n;n;n
                      s/^\s*append\s+initrd=\S+\s+(.*)/\1/Ip;q}'''
        cmd[3:] = [sedscript, self.cfgf]
        self.kcmdline0, err, rc = rcall(cmd)
        self.kcmdline0 = self.kcmdline0.rstrip()


    def _configure_bootloader(self, isodir):
        """Restore the boot configuration files for an iso image boot."""

        isocfgf = os.path.join(isodir, 'isolinux', 'isolinux.cfg')
        if os.path.exists(isocfgf):
            self.cfgf = isocfgf
        else:
            os.rename(self.cfgf, isocfgf)

        cmd = ['sed', '-i', '-r']

        for dn in ('BOOT', 'boot'):
            EFI_config = os.path.join(isodir, 'EFI', dn, 'grub.cfg')
            if os.path.exists(EFI_config):
                break
            else:
                EFI_config = None
        self.EFI_config = EFI_config

        # Keep only the menu entries up through the first submenu.
        sedscript = r'''/\s+}$/ { N
                        /\n}$/ { n;Q}}'''
        cmd.extend([sedscript, EFI_config])
        call(cmd)
        # Remove a netinst Rescue submenu.
        sedscript = r'''/^\s*menuentry 'Rescue .*/I {N;N;N;d}'''
        cmd[3:] = [sedscript, EFI_config]
        call(cmd)

        # Remove labels from any multi boot configurations.
        sedscript = r'''/^\s*label .*/I,/^\s*label linux\>/I{
                        /^\s*label linux\>/I ! {N;N;N;N
                        /\<kernel\s+[^ ]*menu.c32\>/d};}'''
        cmd[3:] = [sedscript, isocfgf]
        call(cmd)
        sedscript = r'''/^\s*menu\s+end/I,$ {
                        /^\s*menu\s+end/I ! d}'''
        cmd[3:] = [sedscript, isocfgf]
        call(cmd)

        sedscript = r'''s/(root=[^ ]*|inst.stage2=[^ ]*)/root=live:CDLABEL={0}/
s/^\s*timeout\s+.*/timeout 600/I
/^\s*totaltimeout\s+.*/Iz
s/\<{1}\>/{2}/g
/^\s*append|linuxefi/I s/\<rd\.live\.[^c]\S+\s+//2g
'''.format(self.fslabel, self.release, self.releasefile)
        cmd = ['sed', '-i', '-r', sedscript, isocfgf]
        if EFI_config:
            cmd.append(EFI_config)
        call(cmd)

        sedscript = r'''s/\<(kernel)\>\s+\S*(vmlinuz.?)/\1 \2/
/\s+append/I {{
s/\<(initrd=).*(initrd.?\.img)\>/\1\2/
s/\s+rw\>//g}}'''
        cmd[3:] = [sedscript, isocfgf]
        call(cmd)

        if EFI_config:
            iso_boot = os.path.join('images', 'pxeboot')
            if not os.path.exists(os.path.join(isodir, iso_boot)):
                iso_boot = 'isolinux'
            sedscript = r'''/^\s*insmod\s+(fat|xfs|f2fs|btrfs)\s*$/ s/(fat|xfs|f2fs|btrfs)/ext2/
s/(--set=root ).+'/\1-l '{0}'/
s/^\s*set\s+timeout=.*/set timeout=60/
s_(linuxefi|initrdefi)\s+\S+(vmlinuz.?|initrd.?\.img)_\1 /{1}/\2_
/^\s*menuentry/ {{
s/\S+\s+~{2}/{2}/
s/\s+rw\>//g}}'''.format(self.fslabel, iso_boot, self.releasefile)
            cmd[3:] = [sedscript, EFI_config]
            call(cmd)

        sedscript = r'''/^\s*append|linuxefi/I {{
'''
        if self.flatten_squashfs:
            self.kernelargs = 'rd.live.overlay.overlayfs ' + self.kernelargs
        if self.kernelargs:
            sedscript += r'''s/\s+(rd\.live\.image|liveimg)(.*)$/ \1 {0} \2/
'''.format(self.kernelargs)
        if self.ks:
            # bootloader --append "!opt-to-remove opt-to-add"
            for param in kickstart.get_kernel_args(self.ks, "").split():
                if param.startswith('!'):
                    param=param[1:]
                    # remove parameter prefixed with !
                    sedscript += r'''s/ %s\>//
''' % param
                else:
                    # append parameter
                    sedscript += r'''s/$/ %s/
''' % param

        sedscript += r''':w;s/(\s+\S+)\s*(.*)\1(\s+|$)/\1 \2\3/g;tw
s/\>\s\s+/ /g
s/\s+$//}}'''
        cmd[3:] = [sedscript, isocfgf]
        if EFI_config:
            cmd.append(EFI_config)

        try:
            call(cmd)
        except IOError as e:
            raise CreatorError("Failed to configure bootloader file: %s" % e)

        # Clear remnants of a multi boot install.
        EFI_config = os.path.join(isodir, 'EFI', 'BOOT', 'grub.cfg.prev')
        if os.path.exists(EFI_config):
            os.remove(EFI_config)
        isocfgf = self.cfgf + '.prev'
        if os.path.exists(isocfgf):
            os.remove(isocfgf)
        # Synchronize redundant configuration files.
        EFI_config = os.path.join(os.path.dirname(EFI_config), 'BOOT.conf')
        shutil.copy2(self.EFI_config, EFI_config)
        EFI_config = EFI_config + '.prev'
        if os.path.exists(EFI_config):
            os.remove(EFI_config)


    def tar_cmd(self, operation, opfile, destdir=None, ops=None):
        """Return arguments for tar call and append tarfile name to list."""

        files0from = None
        if operation == '--create':
            files0from = opfile
            fd, tarfile = tempfile.mkstemp(suffix='.tgz', prefix='se',
                                           dir='%s' % destdir)
            os.close(fd)
            self.seclude_tar.append(tarfile)
        elif operation == '--extract':
            tarfile = opfile
        tar_args = ['tar', operation, '--file=%s' % tarfile, '--gzip',
                    '--one-file-system', '--preserve-permissions', '--xattrs',
                    '--xattrs-include=trusted*', '--selinux', '--acls',
                    '--atime-preserve', '--totals', '--ignore-failed-read']
        if self.src_type in ('OFS', 'embedded-OFS'):
            tar_args.remove('--one-file-system')
        if files0from is not None:
            tar_args.extend(['--null', '--files-from=%s' % files0from])
        if ops is not None:
            tar_args.extend(ops)
        return tar_args


    def seclude_from_isoimage(self):
        """
        Remove user- and machine-specific files.  (This code runs after
        script or shell processing and before the staged filesystem is
        unmounted. The final step, scrub_system() requires the source image
        to be mounted.)
        """
        if self.secludes:
            print('''
            \rFolders and files to be secluded from the new image:
            \r  %s''' % self.secludes)

        se1 = None
        if (not self.clone and not self.src_type == 'iso'):
            # For the .iso build,
            #  Empty these directories;
            [self.secludes.extend([os.path.join(*p + ('*',)),
                                   os.path.join(*p + ('.*',))])
                for p in (('etc', 'blkid'),
                          ('etc', 'X11', 'xorg.conf.d'),
                          ('root',),
                          ('var', 'lib', 'AccountsService', 'users'),
                          ('var', 'lib', 'dhclient'),
                          ('var', 'lib', 'gdm'),
                          ('var', 'lib', 'NetworkManager'),
                          ('var', 'lib', 'sss', 'db'),
                          ('var', 'lib', 'sss', 'mc'),
                          ('var', 'lib', 'upower'),
                          ('var', 'log', 'cups'),
                          ('var', 'log', 'gdm'),
                          ('var', 'log', 'journal'),
                          ('var', 'spool', 'abrt'),
                          ('var', 'spool', 'mail'),
                          ('var', 'spool', 'plymouth'))]

            #  In case the home folder is not in a home.img filesystem.
            if not os.path.isfile(self.home_img):
                self.secludes.extend(['home/*', 'home/.*'])

            #  Remove these directories;
            [self.secludes.append(os.path.join(*p))
                for p in (('var', 'lib', 'systemd'),
                          ('var', 'cache', 'cups'))]

            #  Remove these files;
            [self.secludes.append(os.path.join(*p))
                for p in (('.liveimg-configured',),
                          ('.liveimg-late-configured',),
                          ('.readahead',),
                          ('etc', 'machine-id'),
                          ('etc', 'machine-info'),
                          ('var', 'log', 'cron'),
                          ('var', 'log', 'pm-powersave.log'),
                          ('var', 'log', 'Xorg.*.log'),
                          ('var', 'log', 'Xorg.*.log.old'),
                          ('var', 'log', 'boot.log'),
                          ('var', 'log', 'dmesg'),
                          ('var', 'log', 'dmesg.old'),
                          ('var', 'log', 'audit', 'audit.log'),
                          ('var', 'lib', 'random-seed'),
                          ('var', 'lib', 'alsa', 'asound.state'),
                          ('var', 'lib', 'colord', 'mapping.db'),
                          ('var', 'lib', 'colord', 'storage.db'))]

            #  Seclude these files before editing or replacement for the .iso.
            se1 = [os.path.join('etc', f)
                   for f in ('passwd', 'passwd-', 'group', 'group-', 'shadow',
                             'shadow-', 'gshadow', 'gshadow-',
                             'fstab', 'bashrc')]

            # Seclude these files before they are zeroed for the .iso build.
            se2 = [os.path.join('var', os.sep.join(p))
                   for p in (('log', 'wtmp'),)]
            se2.append(os.path.join('etc', 'resolv.conf'))
            se1.extend(se2)

        if self.secludes:
            instroot = self._instroot

            def build_sec_files0(secludes):
                """Fill a file with null-separated seclude paths for tar."""

                fd, files0from = tempfile.mkstemp(suffix='.0', prefix='se',
                                 dir='%s' % self._ImageCreator__builddir)
                [[os.write(fd, ''.join((p.replace(instroot + os.sep, ''), '\0'
                                      )).encode('utf-8'))
                    for p in pg] for pg in [glob.iglob(fp)
                    for fp in [os.path.join(instroot, sp.lstrip(os.sep))
                    for sp in secludes]]]
                os.close(fd)
                return files0from

            files0 = build_sec_files0(self.secludes)

            print('Secluding files from the new build')
            # Seclude and remove at once.
            tar_args = self.tar_cmd('--create', files0, self.output,
                                    ('--remove-files',))
            out, err, rc = rcall(tar_args, raise_err=False, cwd=instroot)
            print('%s%s' % (out, err))
            if se1 is not None:
                # Seclude and leave for editing.
                tar_args = self.tar_cmd('--create', None, self.output)
                out, err, rc = rcall(tar_args + se1, raise_err=False,
                                     cwd=instroot)
                print('%s%s' % (out, err))

                def zero_file(path):
                    """Write file to zero length."""

                    if os.path.exists(path) and not os.path.islink(path):
                        fd = open(path, 'w')
                        fd.close()

                [zero_file(os.path.join(instroot, p)) for p in se2]

        if not (self.clone or self.skip_seclude or self.src_type == 'iso'):
            self.scrub_system(instroot)


    def scrub_system(self, instroot):
        """
        Remove remnants of account usage and restore some files from the
        previous build.
        """
        #FIXME liveuser specific code
        call(['sed', '-i', '/^liveuser:/d'] + [os.path.join(instroot, 'etc', f)
                for f in ('passwd', 'passwd-', 'shadow', 'shadow-')])
        call(['sed', '-i', '/^liveuser:/d\ns/liveuser//'] +
             [os.path.join(instroot, 'etc', f)
                for f in ('group', 'group-', 'gshadow', 'gshadow-')])

        if self.ovltype == 'DM_linear':
            # No overlay to look below.
            return

        if self.src_type == 'live':
            imgloop = LoopbackDisk(self.imgloop, 0, 'ro')
        else:
            imgloop = self.liveosmnt.imgloop
            if self.liveosmnt.dm_target:
                self.liveosmnt.dm_target.remove()

        # Set up a mount for the base_on original source filesystem.
        prev_sys_mnt = DiskMount(imgloop, self._mkdtemp('prev-'), ops='ro')
        prev_sys_mnt.mount()
        prev_sys = prev_sys_mnt.mountdir

        def copy_if_present(*path):
            """Restore some files from the origin (-1 gen.) source."""
            fpath = os.path.join(prev_sys, *path)
            if os.path.exists(fpath):
                shutil.copy2(fpath, os.path.join(instroot, *path[:-1]))

        [copy_if_present(*fp)
            for fp in (('etc', 'bashrc'), ('etc', 'fstab'),
                       ('etc', 'sudoers'),
                       ('etc', 'yum.conf'),
                       ('etc', 'dnf', 'dnf.conf'),
                       ('etc', 'cups', 'subscriptions.conf'),
                       ('root', '.bash_logout'), ('root', '.bash_profile'),
                       ('root', '.bashrc'), ('root', '.cshrc'),
                       ('root', '.tcshrc'))]
        time.sleep(2)
        prev_sys_mnt.cleanup()

        if self.skip_refresh:
            [os.remove(f) for f in self.seclude_tar]
            self.seclude_tar = []


    def _space_for_refresh(self, isodir, isoliveosdir):
        """
        Check for sufficient space on the installation device for a refresh
        of the root filesystem.
        """
        def call_du(files0from):
            """Return disc usage for files0 files."""
            return int(rcall(['du', '-csb', '--files0-from=-'],
                              files0from, raise_err=False)[0].split()[-2])

        include = ['squashfs.img', self.rootfs_img]
        if self.compress and not self.refresh_uncompressed:
            include.remove(self.rootfs_img)
            img = ''
        else:
            include.remove('squashfs.img')
            if self.flatten_squashfs:
                img = os.path.join(self._LoopImageCreator__imagedir,
                                   self.rootfs_img)
            else:
                img = os.path.join(self._LoopImageCreator__imagedir,
                                   'LiveOS', self.rootfs_img)
            makedirs(isoliveosdir)
        if img:
            shutil.move(img, os.path.join(isoliveosdir, self.rootfs_img))
        files0from = ''
        for fp in [os.path.join(isoliveosdir, fn) for fn in include]:
            files0from = '\0'.join((files0from, fp))
        tobe_copied = call_du(files0from.lstrip('\0'))
        include = ['squashfs.img', self.rootfs_img]
        files0from = ''
        for fp in [os.path.join(self.liveosdir, fn)
            for fn in include]:
            files0from = '\0'.join((files0from, fp))
        tobe_deleted = call_du(files0from.lstrip('\0'))
        locked = unavailable_space(findmnt('-no SOURCE -T', self.srcmntdir))
        home_size = call_du(self.home_img)
        if self.src_type == 'live':
            # rootfs will be unlinked but locked.
            img = self.rootfs_img
            if self.is_squashed:
                img = 'squashfs.img'
            locked += call_du(os.path.join(self.liveosdir, img))
            if hasattr(self, 'newhome_img'):
                locked += home_size
            # Detect new overlays.
            if self.overlay_size_mb or (self.flatten_squashfs and
                                    self.ovltype in ('', 'DM_snapshot_cow')):
                locked += call_du(self._overlay)
        if self.home_size_mb:
            home_size = self.home_size_mb
        delta_overlay = 0
        overlay_size = self.ovl_size
        if self.overlay_size_mb is not None:
            delta_overlay = self.overlay_size_mb - self.ovl_size
            overlay_size = self.overlay_size_mb
        surplus = int(disc_free_info(self.srcmntdir, '-TB1')[4]
                   ) + tobe_deleted - delta_overlay - locked - tobe_copied
        print(''.join(('\n', self.fmt,
                      ' bytes to be copied')).format(tobe_copied))
        print(''.join((self.fmt, ' bytes to be deleted')).format(tobe_deleted))
        print(''.join((self.fmt, ' bytes in home.img')).format(home_size))
        print(''.join((self.fmt, ' bytes in overlay')).format(overlay_size))
        print(''.join((self.fmt, ' bytes locked')).format(locked))
        print(''.join((self.fmt, ' bytes surplus')).format(surplus))
        if surplus <= 0:
            reduced_overlay_size = surplus + overlay_size
            print(''.join((self.fmt, ' bytes allowed in smaller overlay')
                         ).format(reduced_overlay_size))
            self.skip_refresh = True
            return False
        return True


    def refresh(self, iso=None, ops=None):
        """
        (Hijacked either the _LiveImageCreatorBase__implant_md5sum or
        _LiveImageCreatorBase__create_isobase method) to insert code to refresh
        a running or livemounted image's rootfs_img and overlay files.
        """
        if not self.refresh_only:
            # Implant an isomd5sum.
            for c in 'implantisomd5', '/usr/lib/anaconda-runtime/implantisomd5':
                try:
                    subprocess.call([c, iso])
                    break
                except OSError as e:
                    if e.errno == errno.ENOENT:
                        continue
            else:
                logging.warning('isomd5sum not installed; so no mediacheck.')

        if self.skip_refresh or not os.access(self.srcdir, os.W_OK):
            return

        if self.liveossrc:
            try:
                self.liveossrc.mount(dirmode=0o700)
            except MountError as e:
                raise CreatorError("Failed to mount '%s' : '%s'" %
                                   (self.liveossrc.mountdir, e))
        isodir = self._LiveImageCreatorBase__isodir
        isoliveosdir = os.path.join(isodir, 'LiveOS')

        def restore_from_tar(liveosmntdir):
            """Restore the secluded files to a filesystem."""

            def extract_tar(f):
                """Extract files from archive."""

                tar_args = self.tar_cmd('--extract', f, ops=['--overwrite'])
                out, err, rc = rcall(tar_args, raise_err=False,
                                     cwd=liveosmntdir)
                print('%s%s' % (out, err))

            [extract_tar(f) for f in self.seclude_tar]

        call(['sync'])
        if not self._space_for_refresh(isodir, isoliveosdir):
            print('There is insufficient space to perform the requested '
                   'refresh.   Skipping...\n')
            return

        print('''\nRefreshing the source device with the new image.
                   Please wait...''')

        src_dst, dellist = [], []
        def _proclists(delfile, src=None, dst=None):
            """Setup file refresh lists."""
            if delfile:
                fpath = os.path.join(self.liveosdir, delfile)
                if os.path.exists(fpath):
                    dellist.append(fpath)
            if src:
                src_dst.append([os.path.join(isoliveosdir, src),
                                os.path.join(self.liveosdir, dst)])
                if os.path.exists(src_dst[-1][1]):
                    dellist.append(src_dst[-1][1])

        if self.rootfs_img == 'squashfs.img':
            self.rootfs_img = None
        rootfsimg = 'squashfs.img'
        if not self.compress:
            rootfsimg = self.rootfs_img
        if args.rootfsimg:
            rootfsimg = args.rootfsimg

        _proclists('osmin.img')
        if self.compress:
            _proclists(self.rootfs_img, 'squashfs.img', rootfsimg)
        else:
            _proclists('squashfs.img', self.rootfs_img, rootfsimg)

        for f in ('syslinux.cfg', 'extlinux.conf'):
            cfgf = os.path.join(self.bootfolder, f)
            if os.path.exists(cfgf): break
        shutil.copy2(cfgf, cfgf + '.prev')
        cmd = ['sed', '-i', '-r',]
        cmd[3:] = [
        r'''/^\s*menu\s+title\>/I,/^\s*label\s+memtest\>/I s/{0}/{1}/'''.format(
                     self.release, self.releasefile), cfgf]
        call(cmd)

        if os.path.dirname(self.bootfolder) != self.srcmntdir:
            e = os.path.basename(self.liveosdir.rstrip('/'))
            dn = ''
            for c in e:
                if c in r']\/;$*.^?+|{}&[':
                    dn += '\\' + c
                else:
                    dn += c

            f = cfgf.replace(e + os.sep, '')
            shutil.copy2(f, f + '.prev')
            cmd[3:] = [r'''/^\s*label\s+{0}/I {{N;
                      /{0}/ s/{1}/{2}/}}'''.format(
                       dn, self.release, self.releasefile), f]
            call(cmd)
        else:
            dn = ' '

        for c in ('BOOT', 'boot'):
            EFI_config = os.path.join(self.srcmntdir,
                                      'EFI', c, 'grub.cfg')
            if os.path.exists(EFI_config):
                break
        EFI_dir = os.path.join('EFI', c)
        shutil.copy2(EFI_config, EFI_config + '.prev')
        c = os.path.join(os.path.dirname(EFI_config), 'BOOT.conf')
        shutil.copy2(c, c + '.prev')
        cmd[3:] = [r'''/^\s*menuentry/ {{N;
        /{0}\/syslinux\/vmlinuz/ s/{1}/{2}/}}'''.format(
                    dn, self.release, self.releasefile), EFI_config]
        call(cmd)

        # Copy or move new rootfs_img or squashfs.img.
        [os.remove(f) for f in dellist]
        transfer = shutil.copy2
        if os.stat(self.liveosdir).st_dev == os.stat(self.tmpdir).st_dev:
            transfer = shutil.move
        [transfer(*sd) for sd in src_dst]
        call(['sync'])

        if self.ovltype == 'DM_linear' and not self.overlay_size_mb:
            # This signals no overlay processing.
            overlay = 'DM_linear'
        else:
            overlay = args.overlay

        ovlmntdir = self.srcmntdir
        if self.src_type == 'live' and not (self.flatten_squashfs or
                                            self.overlay_size_mb or
                                            self.ovltype == 'DM_linear'):
            self.overlay_size_mb = self.ovl_size
            self.ovl_fstype = self.ovltype
            if self.liveosmnt and self.liveosmnt.ovlmnt:
                self.liveosmnt.ovlmnt.mount()
                ovlmntdir = self.liveosmnt.ovlmntdir
            self.ovl_blksz = 4096
            if self.ovltype not in ('', 'temp','DM_snapshot_cow',
                                    'DM_linear', 'dir'):
                self.ovl_blksz = os.statvfs(
                '/run/initramfs/overlayfs/overlayfs').f_bsize

        if (self.flatten_squashfs and self.ovltype in ('', 'temp', 'tempdir',
            'DM_snapshot_cow')) or (self.overlay_size_mb and
            self.ovltype in ('', 'DM_snapshot_cow', 'DM_linear') and
            self.ovl_fstype not in ('', 'temp','DM_snapshot_cow')):
            # Represents need for a new OverlayFSMount.
            self.ovltype = 'dir'
            if not self.overlay_size_mb:
                self.overlay_size_mb = self.ovl_size
#                self.ovl_fstype = 'dir'
                if self.liveosmnt and self.liveosmnt.ovlmnt:
                    self.liveosmnt.ovlmnt.mount()
                    ovlmntdir = self.liveosmnt.ovlmntdir
            if self.src_fstype == 'vfat' and not self.ovl_fstype:
                self.ovltype = 'embedded-OFS'
                self.ovl_fstype = 'ext4'
                self.ovl_blksz = 4096

            cmd[3:] = [
            's/rd\.live\.image|liveimg/& rd.live.overlay.overlayfs/', cfgf]
            call(cmd)

            cmd[3:] = [r'''/{0}\/syslinux\/vmlinuz/ {{
            s/rd\.live\.image|liveimg/& rd.live.overlay.overlayfs/}}
            '''.format(dn), EFI_config]
            call(cmd)

        elif self.overlay_size_mb and self.ovl_fstype == 'DM_snapshot_cow':
            cmd[3:] = ['s/ rd\.live\.overlay\.overlayfs//g', cfgf]
            call(cmd)

            cmd[3:] = [r'/{0}/ s/ rd\.live\.overlay\.overlayfs//g'.format(dn),
                       EFI_config]
            call(cmd)

        self.liveosmnt = self.new_liveos_mount(self.src, args.rootfsimg,
                                               overlay, self.mntdir)
        self.liveosmnt._LiveImageMount__create(ops='rw', dirmode=0o700)

        if self.overlay_size_mb:
            self.liveosmnt.overlay = None

            overlay = self.liveosmnt.make_overlay(self.overlay_size_mb,
                                                  self.ovl_size,
                                                  self.ovltype,
                                                  self.ovl_fstype,
                                                  self.ovl_blksz)
            self.liveosmnt.cleanup()
            self.liveosmnt.overlay = overlay[0]
            cmd[3:] = [r'''/^\s*append/ {{
            s/rd\.live\.image|liveimg/& rd.live.overlay=UUID={0}/}}
            '''.format(overlay[1]), cfgf]
            call(cmd)
            cmd[3:] = [r'''/{0}\/syslinux\/vmlinuz/ {{
            s/rd\.live\.image|liveimg/& rd.live.overlay=UUID={1}/}}
            '''.format(dn, overlay[1]), EFI_config]
            call(cmd)
        elif self.ovltype != 'DM_linear':
            self.liveosmnt.reset_overlay()

        cmd[3:] = [r'''/^\s*append|linuxefi/I {{
        :w;s/(\s+\S+)\s*(.*)\1(\s+|$)/\1 \2\3/g;tw
        s/\>\s\s+/ /g
        s/\s+$//}}''', cfgf, EFI_config]
        call(cmd)

        shutil.copy2(EFI_config, c)

        self.src_partition = findmnt('-no SOURCE', self.srcmntdir)
        c = lsblk('-no NAME,PKNAME', self.src_partition).split()
        d = '/dev/' + c[1]
        p = c[0][len(c[1]):]
        if p[0] == 'p':
            p = p[1:]
            s = 'p'
        else:
            s = ''

        p = int(p)
        p2 = [d + s + str(p + 1)]
        p3 = [d + s + str(p + 2)]

        def cp_cfg(p):
            e = DiskMount(RawDisk(None, p),
                          tempfile.mkdtemp(dir=self.mntdir, prefix='efi-'),
                          dirmode=0o700)
            e.mount()
            d = e.mountdir
            EFI_config_p = os.path.join(d, EFI_dir, 'grub.cfg')
            if os.access(d, os.W_OK):
                for f in ('BOOT.conf.prev', 'grub.cfg.prev'):
                    shutil.copy2(EFI_config_p, os.path.join(d, EFI_dir, f))
                for f in ('BOOT.conf', 'grub.cfg'):
                    shutil.copy2(EFI_config, os.path.join(d, EFI_dir, f))
                if p == p3[0]:
                    shutil.copy2(EFI_config, os.path.join(d, 'System',
                                        'Library', 'CoreServices', 'grub.cfg'))
                p = True
            else:
                p = False
            e.cleanup()
            return [p, e]

        for p in [[p2, 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b', '0xef'],
                  [p3, '48465300-0000-11aa-aa11-00306543ecac', '0xaf']]:
            c = lsblk('-no NAME,PARTTYPE', p[0][0]).split()
            if c and (c[1] == p[1] or c[1] == p[2]):
                p[0] += cp_cfg(p[0][0])
            else:
                p[0][0] = ''
                p[0] += [False]

        cmd = ['sed', '-n', '-r']
        sedscript = r'''/^\s*label\s+linux/I {n;n;n
                      s/^\s*append\s+initrd=\S+\s+(.*)/\1/Ip;q}'''
        cmd.extend([sedscript, cfgf])
        kcmdline, err, rc = rcall(cmd)
        kcmdline = kcmdline.rstrip()

        # self.kernel_release below is from last invocation of
        #  check_kernel_versions(editor.bootfolder, 'source image')
        if self.src_fstype == 'f2fs' and p2[0]:
            d, err, rc = rcall(['dump.f2fs', self.src_partition])
            if 'extra_attr' in d:
                p2[2].mount()
                d = p2[2].mountdir
                if dn == ' ':
                    dn = 'LiveOS'
                e = os.path.join(d, 'linux_' + dn + '.efi')
                cmd = ['sed', '-znr']
                cmd[2:] = [r'''1,/^[0-9]+\.[0-9]+\.[0-9]+/ {
                s/^([0-9]+\.[0-9]+\.[0-9]+\S+)\s+.*/\1/p}''', e]
                efistub_kernel, err, rc = rcall(cmd)
                efistub_kernel = efistub_kernel.rstrip('\x00')

                if (kcmdline != self.kcmdline0 or (efistub_kernel and
                    self.kernel_release != efistub_kernel)):
                    self.liveosmnt.mount()
                    dracut_cmd = ['dracut', e, '--uefi', '--kmoddir',
                    os.path.join(self.liveosmnt.mountdir, 'usr', 'lib',
                    'modules', self.kernel_release), '--kver',
                    self.kernel_release, '--no-machineid', '--kernel-image',
                    os.path.join(self.bootfolder, 'vmlinuz'), '--no-hostonly',
                    '--add', 'dmsquash-live', '--add-drivers', 'f2fs',
                    '--kernel-cmdline', kcmdline, '--force']
                    print('\nUpdating %s using this command:\n\n%s' %
                          (os.path.basename(e), dracut_cmd))
                    call(dracut_cmd)
                p2[2].cleanup()

        if self.seclude_tar:
            print('Restoring secluded files to the refresh...')
            self.liveosmnt.mount('rw')
            restore_from_tar(self.liveosmnt.mountdir)
            [os.remove(f) for f in self.seclude_tar]
#           TODO Implement homefsck support, such as below.
#            if self.src_type == 'live' and os.path.exists(self.home_img):
#                # Tag to signal a home fsck, if implemented in startup script.
#                fd = open(os.path.join(self.liveosmnt.mountdir,
#                                       'forcehomefsck'), 'w')
#                fd.close()

        if hasattr(self, 'newhome_img'):
            self.newhomemnt.cleanup()

        call(self.dmsetup_cmd + ['mknodes'])
        call(['sync'])
        self.liveosmnt.cleanup()
        if self.liveossrc:
            self.liveossrc.cleanup()
        if p2[0] and p2[1]:
            self._fsck_img(p2[0], 'vfat')
        if p3[0] and p3[1]:
            self._fsck_img(p3[0], 'hfsplus')


    def _fsck_img(self, img, fstype):
        """Check and report on a filesystem."""

        print('  Checking filesystem: %s' % img)
        if fsck(img, fstype) != 0:
            print('Attempt 1: fsck of %s detected some errors.' % img)
            if fsck(img, fstype) != 0:
                raise CreatorError('fsck of %s failed!' % img)
            else:
                print('Attempt 2: passed.')


    def _unmount_instroot(self):
        """Undo anything performed in mount()."""

        call(['sync'])
        if self.newhomemnt:
            self.newhomemnt.cleanup()
        if self._LoopImageCreator__instloop:
            self._LoopImageCreator__instloop.cleanup()


    class simpleCallback:
        def __init__(self):
            self.fdnos = {}

        def callback(self, what, amount, total, mydata, wibble):
            if what == rpm.RPMCALLBACK_TRANS_START:
                pass

            elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
                hdr, path = mydata
                print("Installing %s\r" % (hdr["name"]))
                fd = os.open(path, os.O_RDONLY)
                nvr = '%s-%s-%s' % ( hdr['name'], hdr['version'],
                                     hdr['release'] )
                self.fdnos[nvr] = fd
                return fd

            elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
                hdr, path = mydata
                nvr = '%s-%s-%s' % ( hdr['name'], hdr['version'],
                                     hdr['release'] )
                os.close(self.fdnos[nvr])

            elif what == rpm.RPMCALLBACK_INST_PROGRESS:
                hdr, path = mydata
                print("%s:  %.5s%% done\r" % (hdr["name"],
                                              (float(amount) / total) * 100),)

def copypaths(pathlist, dst_dir, ignore_list=[], parents=None, message=None):
    """
    Copy files in a list of filepaths to a destination directory including
    any filepath parent directories, if 'parents' is set to True.  Ignore
    any patterns in the ignore_list, and report a message, if provided.
    """
    tgt_list = []
    dst_dir0 = dst_dir
    for fp in pathlist:
        if os.path.exists(fp):
            dst_dir = dst_dir0
            if parents:
                tgt_dir = os.path.join(dst_dir,
                                       os.path.dirname(fp).lstrip(os.sep))
                makedirs(tgt_dir)
                dst_dir = tgt_dir
            tgt_list += [os.path.basename(fp)]
            if os.path.isfile(fp):
                try:
                    shutil.copy2(fp, os.path.join(dst_dir, tgt_list[-1]))
                except OSError as e:
                    raise CreatorError("Failed to copy '%s': %s" % (fp, e))
            elif os.path.isdir(fp):
                try:
                    shutil.copytree(fp, os.path.join(dst_dir, tgt_list[-1]),
                                    symlinks=True,
                                    ignore=shutil.ignore_patterns(
                                        *ignore_list))
                except OSError as e:
                    raise CreatorError("Failed to copy '%s': %s" % (fp, e))
    if tgt_list and message:
        print("\n%s\n  %s" % (message, tgt_list))


def main():
    success = None

    if os.geteuid () != 0:
        print("You must run editliveos with root privileges.", file=sys.stderr) 
        return 1
    if args.script:
        if not os.path.exists(args.script):
            print('Invalid script path, %s' % args.script)
            return 1
    if args.home_size_mb:
        homefs = args.home_size_mb.split(',')
        try:
            int(homefs[0])
        except ValueError as e:
            raise CreatorError('\nNotice: --home-size-mb: %s is not a valid '
            'number.\n     Please correct this.\nError: %s' % (
                homefs[0], e))
        if len(homefs) > 2:
            try:
                int(homefs[2])
            except ValueError as e:
                raise CreatorError('\nNotice: home-blksz: %s is not a valid '
                'number.\n     Please correct this.\nError: %s' % (
                    homefs[2], e))
            else:
                checkfsblksz(homefs[1], int(homefs[2]), 'home', int(homefs[0]))
    if args.overlay_size_mb:
        ovlfs = args.overlay_size_mb.split(',')
        try:
            int(ovlfs[0])
        except ValueError as e:
            raise CreatorError('\nNotice: overlay-size-mb: %s is not a '
            'valid number.\n     Please correct this.\nError: %s' % (
                ovlfs[0], e))
        if len(ovlfs) > 2:
            try:
                int(ovlfs[2])
            except ValueError as e:
                raise CreatorError('\nNotice: ovl-blksz: %s is not a valid '
                'number.\n     Please correct this.\nError: %s' % (
                    ovlfs[2], e))
            else:
                checkfsblksz(ovlfs[1], int(ovlfs[2]), 'ovl', int(ovlfs[0]))
    if args.rootfs_size:
        rootfs = args.rootfs_size.split(',')
        try:
            int(rootfs[0])
        except ValueError as e:
            raise CreatorError('\nNotice: --rootfs-size-gb: %s is not a '
            'valid number.\n     Please correct this.\nError: %s' % (
                rootfs[0], e))
        if len(rootfs) > 2:
            try:
                int(rootfs[2])
            except ValueError as e:
                raise CreatorError('\nNotice: rootfs-blksz: %s is not a valid '
                'number.\n     Please correct this.\nError: %s' % (
                    rootfs[2], e))
            else:
                checkfsblksz(rootfs[1], int(rootfs[2]), 'dev', int(rootfs[0]))

    name = ''.join((os.path.basename(args.liveos.rstrip('/')), '.edited'))
    src_type = 'iso'
    src_fstype = 'iso9660'
    if args.liveos == 'live' or args.liveos in (
                 '/mnt/live', '/run/initramfs/live', '/run/initramfs/livedev'):
        src_type = 'live'
        src_fstype = findmnt('-no FSTYPE -T', '/run/initramfs/live')
    else:
        try:
            st_mode = os.stat(args.liveos).st_mode
        except OSError as e:
            raise CreatorError('''\nThere seems to be a problem with '%s'
            \r    Please check this.\nError: %s''' % (args.liveos, e))
        if stat.S_ISDIR(st_mode):
            src_type = 'dir'
            with open('/proc/cmdline', 'rb') as f:
                cmdline = f.read()
            if b'rd.live.image' in cmdline:
                src = os.path.dirname(losetup('-nO BACK-FILE', '/dev/loop0'))
                if src and os.path.samefile(src, args.liveos):
                    src_type = 'live'
            src_fstype = findmnt('-no FSTYPE -T', args.liveos)
        elif stat.S_ISBLK(st_mode):
            if lsblk('-ndo FSTYPE', args.liveos) != 'iso9660':
                src_type = 'blk'
                src = findmnt('-no TARGET', args.liveos).split('\n')
                if src in ('/run/initramfs/live', '/mnt/live'):
                    src_type = 'live'
            src_fstype = lsblk('-no FSTYPE', args.liveos)
            name = ''.join((findmnt('-no LABEL', args.liveos), '.edited'))
        elif stat.S_ISREG(st_mode) and args.overlay_size_mb:
            print("\nNOTICE:  An overlay is unsuitable for '%s'.\n" % 
                  args.liveos, file=sys.stderr) 
            return 1
    if src_fstype == 'f2fs' and not args.skip_refresh and not os.path.exists(
        os.path.join('/', 'usr', 'sbin', 'fsck.f2fs')):
        print('''
        \rNOTICE:  The host system must have the 'f2fs-tools' package
         installed in order to complete this editliveos session.\n
         Run 'sudo dnf install f2fs-tools'.\n''')
        return 1
    if args.name:
        name = args.name
        if any(n in os.sep for n in name):
            print("\nALERT:\n\tThe proposed name ' ", name,
            " '\n\tcontains the os separator '", os.sep, "',\n"
            '\twhich is incompatible with an .iso file name.\n\nAttempting '
            'to rename it by replacing said separator with underscores_...\n',
            sep='')
            name = name.translate(str.maketrans(os.sep, '_'))
            print('\t', name, '\n')
    if args.output:
        output = args.output
    elif src_type == 'iso':
        output = os.path.dirname(os.path.normpath(args.liveos))
    elif src_type in ('live', 'dir', 'blk'):
        output = args.tmpdir

    editor = LiveImageEditor(name, docleanup=not args.nocleanup)
    editor.src = args.liveos
    editor.src_type = src_type
    editor.src_fstype = src_fstype
    editor.exclude_all = False
    if args.excludes:
        editor.excludes = args.excludes.split()
    else:
        editor.exclude_all = True
    if args.includes:
        editor.includes = args.includes.split()
    if args.secludes:
        editor.secludes = args.secludes.split()
    editor.dmsetup_cmd = ['dmsetup']
    if '--noudevsync' in rcall(['dmsetup', '-h'])[1]:
        editor.dmsetup_cmd = ['dmsetup', '--noudevrules', '--noudevsync']
    editor.force_selinux = args.force_selinux
    editor.script = args.script
    editor.shell = args.shell
    editor.clone = args.clone
    editor.rootfs_size = args.rootfs_size
    if args.rootfs_size:
        editor.rootfs_size = rootfs[0]
        try:
            editor.root_fstype = rootfs[1]
        except IndexError:
            editor.root_fstype = 'ext4'
        try:
            editor.root_blksz = rootfs[2]
        except IndexError:
            editor.root_blksz = 4096
    editor.home_size_mb = args.home_size_mb
    if args.home_size_mb:
        editor.home_size_mb = homefs[0]
        try:
            editor.home_fstype = homefs[1]
        except IndexError:
            editor.home_fstype = 'ext4'
        try:
            editor.home_blksz = homefs[2]
        except IndexError:
            editor.home_blksz = 4096
    editor.EncHomeReq = args.EncHomeReq
    editor.overlay_size_mb = args.overlay_size_mb
    if args.overlay_size_mb:
        editor.overlay_size_mb = ovlfs[0]
        try:
            editor.ovl_fstype = ovlfs[1]
        except IndexError:
            editor.ovl_fstype = ''
        try:
            editor.ovl_blksz = ovlfs[2]
        except IndexError:
            editor.ovl_blksz = 4096
        if editor.ovl_fstype == 'dir' and editor.src_fstype in ('vfat',
                                                                'msdos'):
            print("\nNOTICE:\tAn unembedded directory overlay is unsuitable\n"
                  "\tfor the '%s'-based device '%s'.\n" % (editor.src_fstype,
                  args.liveos), file=sys.stderr)
            return 1
    editor.refresh_only = args.refresh_only
    if editor.refresh_only and editor.src_type in ('iso'):
        print("\nNOTICE:\t--refresh-only is not possible for a read-only "
              "source like\n\t    '%s'.\n\tA new .iso will be built.\n" %
              (args.liveos), file=sys.stderr)
        # Ignore impossible request.
        editor.refresh_only = False
    editor.skip_refresh = args.skip_refresh
    editor.skip_seclude = args.skip_seclude
    editor.tmpdir = args.tmpdir
    editor.docleanup = not args.nocleanup
    editor.cachedir = args.cachedir
    editor.output = output
    editor.builder = args.builder
    if args.releasefile:
        editor.releasefile = args.releasefile.split()
    editor.compress_args = args.compress_type
    editor.refresh_uncompressed = args.refresh_uncompressed
    editor.skip_compression = args.skip_compression
    editor.compress = args.compress
    editor.flatten_squashfs = args.flatten_squashfs
    if (editor.flatten_squashfs and editor.overlay_size_mb and
        editor.ovl_fstype == 'DM_snapshot_cow'):
        print("\nNOTICE:\tA flattened squashfs is incompatible with a Device-"
              "mapper type overlay.\n", file=sys.stderr)
        return 1
    editor.kernelargs = args.kernelargs
    editor.extra_space = int(args.extra_space_mb) * 1024 ** 2

    try:
        if args.kscfg:
            editor.ks = kickstart.read_kickstart(args.kscfg)

            editor.excludeWeakdeps = kickstart.exclude_weakdeps(editor.ks)
            editor.releasever = args.releasever
            editor.useplugins = args.plugins
            editor.cacheonly = args.cacheonly

            # part / --size <new rootfs size to be resized to>
            editor._LoopImageCreator__image_size = kickstart.get_image_size(
                                                   editor.ks)
        editor._pre_mount(args.liveos, args.rootfsimg, args.overlay)
        editor.mount(editor.cachedir)
        isodir = editor.bootfolder
        editor.kernels = None
        editor.check_kernel_versions(editor.bootfolder, 'current image')
        editor.check_kernel_versions(os.path.join(editor.imagesdir, 'pxeboot'),
            'current .iso')
        editor.kernel_version0 = editor.kernel_release
        editor.initrd_kernel0 = editor.initrd_kernel
        editor.kernels0 = editor.kernels
        if not editor.refresh_only:
            isodir = os.path.join(editor._LiveImageCreatorBase__isodir,
                                  'isolinux')
        editor._brand(isodir)
        if not editor.refresh_only:
            editor._configure_bootloader(editor._LiveImageCreatorBase__isodir)
        if editor.ks:
            # Run_pre_scripts same code as ImageCreator_run_post_scripts
            # editor._run_post_scripts()
            editor.install()
            editor._run_post_scripts()
        elif args.script:
            print("Running edit script '%s'" % args.script)
            editor._run_script(args.script)
        if editor.shell:
            print('''Launching shell. Exit (Ctrl D) to continue.
                  \r-------------------------------------------''')
            ops = None
            editor.launch_shell()

        editor.kernels = None
        editor.check_kernel_versions(isodir, 'update')
        if not editor.refresh_only:
            editor.check_kernel_versions(os.path.join(
                editor._LiveImageCreatorBase__isodir, 'images', 'pxeboot'),
                'new .iso')
            editor.check_kernel_versions(isodir, 'new install image')
        if ((not editor.src_type == 'iso' or editor.skip_refresh) and
            not hasattr(editor, 'isosrcmnt')):
            editor.check_kernel_versions(os.path.join(editor.imagesdir,
                'pxeboot'), 'source PXE')
            editor.check_kernel_versions(editor.bootfolder, 'source image')
        if editor.liveosmnt:
            if editor.refresh_only:
                editor.liveosmnt.cleanup()
            else:
                editor.liveosmnt.unmount()
        if editor.compress is None:
            if editor.is_squashed:
                editor.compress = True
            else:
                editor.refresh_uncompressed = True
        if editor.refresh_only:
            if editor.refresh_uncompressed:
                editor.compress = False
                ImageCreator.package = editor.refresh
                if not editor.flatten_squashfs:
                    imgdir = os.path.join(
                                  editor._LoopImageCreator__imagedir, 'LiveOS')
                    makedirs(imgdir)
                    shutil.move(editor._image,
                                os.path.join(imgdir, editor.rootfs_img))
            else:
                editor._LiveImageCreatorBase__create_iso = editor.refresh
                if not editor.compress:
                    editor.skip_compression = True
                if not editor.skip_compression:
                    print('''\nThe new image will now be resquashed.
                    Please wait...''')
        if not editor.refresh_only and not editor.skip_seclude:
            editor.seclude_from_isoimage()
        if editor.liveosmnt:
            editor.liveosmnt.cleanup()

        editor.unmount()

        if editor.src_type == 'iso':
            editor.liveosmnt.imgloop.cleanup()
            editor.liveossrc.cleanup()
        editor._LiveImageCreatorBase__implant_md5sum = editor.refresh
        print('The new image will now be packaged. Please wait...')
        ops = ['show-squashing']
        if editor.flatten_squashfs:
            ops += ['flatten-squashfs']
        # package() calls refresh(), if requested.
        editor.package(output, ops)

        if not editor.refresh_only:
            print("\n%s.iso saved to %s"  % (editor.name, editor.output))
            logging.info("%s.iso saved to %s"  % (editor.name, editor.output))
        success = True
    except CreatorError as e:
        logging.error(u"Error editing LiveOS : '%s'" % e)
        print("\nError editing LiveOS: '%s'" % e)
        success = False
        return 1
    finally:
        if editor.liveosmnt:
            editor.liveosmnt.cleanup()
        editor.cleanup()
        if (editor.src_type == 'live' and not editor.skip_refresh and
         editor.src_fstype == 'vfat' and os.access(editor.liveosmnt.srcdir,
                                                   os.W_OK)):
            print('''
            NOTICE:  Please shut down and run fsck.vfat on this device's
                     partition, '%s', to reclaim storage clusters that were
                     unlinked during the LiveOS and overlay refresh.
                  ''' % editor.src_partition)
        if (editor.src_type not in ('dir', 'live') and editor.srcmntdir
            and os.path.ismount(editor.srcmntdir)):
            os.system('umount ' + editor.srcmntdir)
        if hasattr(editor, 'isosrcmnt'):
            editor.isosrcmnt.unmount()
            editor.liveossrc.cleanup()
        if (success and not editor.skip_refresh and
            not editor.src_type in ('live', 'iso')):
            editor._fsck_img(editor.src_partition, editor.src_fstype)
        if success and editor.docleanup and editor.src_type != 'iso':
            shutil.rmtree(editor.mntdir)
        print('\nLiveOS edit has ended.')

        h, m = divmod(time.time() - t0, 3600)
        m, s = divmod(m, 60)
        print('Process duration: %02d:%02d:%02d' % (h, m, s))

    return 0

if __name__ == "__main__":
    sys.exit(main())

if args.arch in ("i386", "x86_64"):
    LiveImageCreator = x86LiveImageCreator
elif args.arch in ("ppc",):
    LiveImageCreator = ppcLiveImageCreator
elif args.arch in ("ppc64",):
    LiveImageCreator = ppc64LiveImageCreator
elif args.arch.startswith(("arm", "aarch64")):
    LiveImageCreator = LiveImageCreatorBase
elif args.arch in ("riscv64",):
    LiveImageCreator = LiveImageCreatorBase
else:
    raise CreatorError("Architecture not supported!")
