[PATCHv2] Move autopart and installation-specific code outside of __init__.py

David Lehman dlehman at redhat.com
Fri Feb 13 18:32:49 UTC 2015


On 02/13/2015 03:39 AM, Vratislav Podzimek wrote:
> This code belongs to separate modules.
>
> <not-in-commit-msg>
> The difference from the first version of the patch is that the
> turnOnFilesystems and writeEscrowPackets functions were moved to the
> blivet/osinstall.py module and both osinstall.py and autopart.py now
> have the right author mentioned in the header.
>
> I'm not sending a new version of the related anaconda as it only has a
> single change of import from blivet (turnOnFilesystems) added on top
> of the old patch.
>
> Both pylint and tests are okay in both anaconda and blivet.
> </not-in-commit-msg>
>
> Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>

ACK.

> ---
>   blivet/__init__.py        | 1083 +-------------------------------------------
>   blivet/autopart.py        |  483 ++++++++++++++++++++
>   blivet/devicelibs/swap.py |   67 ---
>   blivet/osinstall.py       | 1107 +++++++++++++++++++++++++++++++++++++++++++++
>   blivet/partitioning.py    |  386 +---------------
>   5 files changed, 1602 insertions(+), 1524 deletions(-)
>   create mode 100644 blivet/autopart.py
>   create mode 100644 blivet/osinstall.py
>
> diff --git a/blivet/__init__.py b/blivet/__init__.py
> index 3c12afd..8e1c0bd 100644
> --- a/blivet/__init__.py
> +++ b/blivet/__init__.py
> @@ -50,32 +50,22 @@ get_bootloader = lambda: None
>
>   import os
>   import time
> -import stat
>   import copy
>   import tempfile
> -import shlex
>   import re
>
> -try:
> -    import nss.nss
> -except ImportError:
> -    nss = None
> -
>   import parted
>
>   from pykickstart.constants import AUTOPART_TYPE_LVM, CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_LIST, CLEARPART_TYPE_NONE
>
>   from .storage_log import log_exception_info, log_method_call
> -from .errors import DeviceError, FSResizeError, FSTabTypeMismatchError, UnknownSourceDeviceError, StorageError, UnrecognizedFSTabEntryError
> -from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice, DirectoryDevice, FileDevice, LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice, MDRaidArrayDevice, NetworkStorageDevice, NFSDevice, NoDevice, OpticalDevice, PartitionDevice, TmpFSDevice, devicePathToName
> +from .errors import UnknownSourceDeviceError, StorageError
> +from .devices import BTRFSDevice, BTRFSSubVolumeDevice, BTRFSVolumeDevice, LVMLogicalVolumeDevice, LVMThinLogicalVolumeDevice, LVMThinPoolDevice, LVMVolumeGroupDevice, MDRaidArrayDevice, PartitionDevice, TmpFSDevice, devicePathToName
>   from .devicetree import DeviceTree
>   from .deviceaction import ActionCreateDevice, ActionCreateFormat, ActionDestroyDevice, ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat
>   from .formats import getFormat
> -from .formats import get_device_format_class
>   from .formats import get_default_filesystem_type
>   from . import devicefactory
> -from .devicelibs.dm import name_from_dm_node
> -from .devicelibs.crypto import generateBackupPassphrase
>   from .devicelibs.edd import get_edd_dict
>   from .devicelibs.dasd import make_dasd_list, write_dasd_conf
>   from . import iscsi
> @@ -184,73 +174,6 @@ def storageInitialize(storage, ksdata, protected):
>                                            if d.name not in ksdata.ignoredisk.ignoredisk]
>               log.debug("onlyuse is now: %s", ",".join(ksdata.ignoredisk.onlyuse))
>
> -def turnOnFilesystems(storage, mountOnly=False, callbacks=None):
> -    """
> -    Perform installer-specific activation of storage configuration.
> -
> -    :param callbacks: callbacks to be invoked when actions are executed
> -    :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
> -
> -    """
> -
> -    if not flags.installer_mode:
> -        return
> -
> -    if not mountOnly:
> -        if (flags.live_install and not flags.image_install and not storage.fsset.active):
> -            # turn off any swaps that we didn't turn on
> -            # needed for live installs
> -            util.run_program(["swapoff", "-a"])
> -        storage.devicetree.teardownAll()
> -
> -        try:
> -            storage.doIt(callbacks)
> -        except FSResizeError as e:
> -            if errorHandler.cb(e) == ERROR_RAISE:
> -                raise
> -        except Exception as e:
> -            raise
> -
> -        storage.turnOnSwap()
> -    # FIXME:  For livecd, skipRoot needs to be True.
> -    storage.mountFilesystems()
> -
> -    if not mountOnly:
> -        writeEscrowPackets(storage)
> -
> -def writeEscrowPackets(storage):
> -    escrowDevices = [d for d in storage.devices if d.format.type == 'luks' and
> -                     d.format.escrow_cert]
> -
> -    if not escrowDevices:
> -        return
> -
> -    log.debug("escrow: writeEscrowPackets start")
> -
> -    if not nss:
> -        log.error("escrow: no nss python module -- aborting")
> -        return
> -
> -    nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized
> -
> -    backupPassphrase = generateBackupPassphrase()
> -
> -    try:
> -        escrowDir = _sysroot + "/root"
> -        log.debug("escrow: writing escrow packets to %s", escrowDir)
> -        util.makedirs(escrowDir)
> -        for device in escrowDevices:
> -            log.debug("escrow: device %s: %s",
> -                      repr(device.path), repr(device.format.type))
> -            device.format.escrow(escrowDir,
> -                                 backupPassphrase)
> -
> -    except (IOError, RuntimeError) as e:
> -        # TODO: real error handling
> -        log.error("failed to store encryption key: %s", e)
> -
> -    log.debug("escrow: writeEscrowPackets done")
> -
>   def empty_device(device, devicetree):
>       empty = True
>       if device.partitioned:
> @@ -302,6 +225,10 @@ class Blivet(object):
>               :keyword ksdata: kickstart data store
>               :type ksdata: :class:`pykickstart.Handler`
>           """
> +        # XXX: this cannot be imported globally as it would create a dependency
> +        # cycle in the imports
> +        from .osinstall import FSSet
> +
>           self.ksdata = ksdata
>           self._bootloader = None
>
> @@ -442,6 +369,11 @@ class Blivet(object):
>               See :meth:`devicetree.Devicetree.populate` for more information
>               about the cleanupOnly keyword argument.
>           """
> +
> +        # XXX: this cannot be imported globally as it would create a dependency
> +        # cycle in the imports
> +        from .osinstall import findExistingInstallations, FSSet
> +
>           log.info("resetting Blivet (version %s) instance %s", __version__, self)
>           if flags.installer_mode:
>               # save passphrases for luks devices so we don't have to reprompt
> @@ -2072,996 +2004,3 @@ def mountExistingSystem(fsset, rootDevice, readOnly=None):
>
>       fsset.parseFSTab()
>       fsset.mountFilesystems(rootPath=_sysroot, readOnly=readOnly, skipRoot=True)
> -
> -
> -class BlkidTab(object):
> -    """ Dictionary-like interface to blkid.tab with device path keys """
> -    def __init__(self, chroot=""):
> -        self.chroot = chroot
> -        self.devices = {}
> -
> -    def parse(self):
> -        path = "%s/etc/blkid/blkid.tab" % self.chroot
> -        log.debug("parsing %s", path)
> -        with open(path) as f:
> -            for line in f.readlines():
> -                # this is pretty ugly, but an XML parser is more work than
> -                # is justifiable for this purpose
> -                if not line.startswith("<device "):
> -                    continue
> -
> -                line = line[len("<device "):-len("</device>\n")]
> -
> -                (data, _sep, device) = line.partition(">")
> -                if not device:
> -                    continue
> -
> -                self.devices[device] = {}
> -                for pair in data.split():
> -                    try:
> -                        (key, value) = pair.split("=")
> -                    except ValueError:
> -                        continue
> -
> -                    self.devices[device][key] = value[1:-1] # strip off quotes
> -
> -    def __getitem__(self, key):
> -        return self.devices[key]
> -
> -    def get(self, key, default=None):
> -        return self.devices.get(key, default)
> -
> -
> -class CryptTab(object):
> -    """ Dictionary-like interface to crypttab entries with map name keys """
> -    def __init__(self, devicetree, blkidTab=None, chroot=""):
> -        self.devicetree = devicetree
> -        self.blkidTab = blkidTab
> -        self.chroot = chroot
> -        self.mappings = {}
> -
> -    def parse(self, chroot=""):
> -        """ Parse /etc/crypttab from an existing installation. """
> -        if not chroot or not os.path.isdir(chroot):
> -            chroot = ""
> -
> -        path = "%s/etc/crypttab" % chroot
> -        log.debug("parsing %s", path)
> -        with open(path) as f:
> -            if not self.blkidTab:
> -                try:
> -                    self.blkidTab = BlkidTab(chroot=chroot)
> -                    self.blkidTab.parse()
> -                except Exception: # pylint: disable=broad-except
> -                    log_exception_info(fmt_str="failed to parse blkid.tab")
> -                    self.blkidTab = None
> -
> -            for line in f.readlines():
> -                (line, _pound, _comment) = line.partition("#")
> -                fields = line.split()
> -                if not 2 <= len(fields) <= 4:
> -                    continue
> -                elif len(fields) == 2:
> -                    fields.extend(['none', ''])
> -                elif len(fields) == 3:
> -                    fields.append('')
> -
> -                (name, devspec, keyfile, options) = fields
> -
> -                # resolve devspec to a device in the tree
> -                device = self.devicetree.resolveDevice(devspec,
> -                                                       blkidTab=self.blkidTab)
> -                if device:
> -                    self.mappings[name] = {"device": device,
> -                                           "keyfile": keyfile,
> -                                           "options": options}
> -
> -    def populate(self):
> -        """ Populate the instance based on the device tree's contents. """
> -        for device in self.devicetree.devices:
> -            # XXX should we put them all in there or just the ones that
> -            #     are part of a device containing swap or a filesystem?
> -            #
> -            #       Put them all in here -- we can filter from FSSet
> -            if device.format.type != "luks":
> -                continue
> -
> -            key_file = device.format.keyFile
> -            if not key_file:
> -                key_file = "none"
> -
> -            options = device.format.options or ""
> -
> -            self.mappings[device.format.mapName] = {"device": device,
> -                                                    "keyfile": key_file,
> -                                                    "options": options}
> -
> -    def crypttab(self):
> -        """ Write out /etc/crypttab """
> -        crypttab = ""
> -        for name in self.mappings:
> -            entry = self[name]
> -            crypttab += "%s UUID=%s %s %s\n" % (name,
> -                                                entry['device'].format.uuid,
> -                                                entry['keyfile'],
> -                                                entry['options'])
> -        return crypttab
> -
> -    def __getitem__(self, key):
> -        return self.mappings[key]
> -
> -    def get(self, key, default=None):
> -        return self.mappings.get(key, default)
> -
> -def get_containing_device(path, devicetree):
> -    """ Return the device that a path resides on. """
> -    if not os.path.exists(path):
> -        return None
> -
> -    st = os.stat(path)
> -    major = os.major(st.st_dev)
> -    minor = os.minor(st.st_dev)
> -    link = "/sys/dev/block/%s:%s" % (major, minor)
> -    if not os.path.exists(link):
> -        return None
> -
> -    try:
> -        device_name = os.path.basename(os.readlink(link))
> -    except Exception: # pylint: disable=broad-except
> -        log_exception_info(fmt_str="failed to find device name for path %s", fmt_args=[path])
> -        return None
> -
> -    if device_name.startswith("dm-"):
> -        # have I told you lately that I love you, device-mapper?
> -        device_name = name_from_dm_node(device_name)
> -
> -    return devicetree.getDeviceByName(device_name)
> -
> -
> -class FSSet(object):
> -    """ A class to represent a set of filesystems. """
> -    def __init__(self, devicetree):
> -        self.devicetree = devicetree
> -        self.cryptTab = None
> -        self.blkidTab = None
> -        self.origFStab = None
> -        self.active = False
> -        self._dev = None
> -        self._devpts = None
> -        self._sysfs = None
> -        self._proc = None
> -        self._devshm = None
> -        self._usb = None
> -        self._selinux = None
> -        self._run = None
> -        self._fstab_swaps = set()
> -        self.preserveLines = []     # lines we just ignore and preserve
> -
> -    @property
> -    def sysfs(self):
> -        if not self._sysfs:
> -            self._sysfs = NoDevice(fmt=getFormat("sysfs", device="sysfs", mountpoint="/sys"))
> -        return self._sysfs
> -
> -    @property
> -    def dev(self):
> -        if not self._dev:
> -            self._dev = DirectoryDevice("/dev",
> -               fmt=getFormat("bind", device="/dev", mountpoint="/dev", exists=True),
> -               exists=True)
> -
> -        return self._dev
> -
> -    @property
> -    def devpts(self):
> -        if not self._devpts:
> -            self._devpts = NoDevice(fmt=getFormat("devpts", device="devpts", mountpoint="/dev/pts"))
> -        return self._devpts
> -
> -    @property
> -    def proc(self):
> -        if not self._proc:
> -            self._proc = NoDevice(fmt=getFormat("proc", device="proc", mountpoint="/proc"))
> -        return self._proc
> -
> -    @property
> -    def devshm(self):
> -        if not self._devshm:
> -            self._devshm = NoDevice(fmt=getFormat("tmpfs", device="tmpfs", mountpoint="/dev/shm"))
> -        return self._devshm
> -
> -    @property
> -    def usb(self):
> -        if not self._usb:
> -            self._usb = NoDevice(fmt=getFormat("usbfs", device="usbfs", mountpoint="/proc/bus/usb"))
> -        return self._usb
> -
> -    @property
> -    def selinux(self):
> -        if not self._selinux:
> -            self._selinux = NoDevice(fmt=getFormat("selinuxfs", device="selinuxfs", mountpoint="/sys/fs/selinux"))
> -        return self._selinux
> -
> -    @property
> -    def run(self):
> -        if not self._run:
> -            self._run = DirectoryDevice("/run",
> -               fmt=getFormat("bind", device="/run", mountpoint="/run", exists=True),
> -               exists=True)
> -
> -        return self._run
> -
> -    @property
> -    def devices(self):
> -        return sorted(self.devicetree.devices, key=lambda d: d.path)
> -
> -    @property
> -    def mountpoints(self):
> -        filesystems = {}
> -        for device in self.devices:
> -            if device.format.mountable and device.format.mountpoint:
> -                filesystems[device.format.mountpoint] = device
> -        return filesystems
> -
> -    def _parseOneLine(self, devspec, mountpoint, fstype, options, _dump="0", _passno="0"):
> -        """Parse an fstab entry for a device, return the corresponding device.
> -
> -           The parameters correspond to the items in a single entry in the
> -           order in which they occur in the entry.
> -
> -           :returns: the device corresponding to the entry
> -           :rtype: :class:`devices.Device`
> -        """
> -
> -        # no sense in doing any legwork for a noauto entry
> -        if "noauto" in options.split(","):
> -            log.info("ignoring noauto entry")
> -            raise UnrecognizedFSTabEntryError()
> -
> -        # find device in the tree
> -        device = self.devicetree.resolveDevice(devspec,
> -                                               cryptTab=self.cryptTab,
> -                                               blkidTab=self.blkidTab,
> -                                               options=options)
> -
> -        if device:
> -            # fall through to the bottom of this block
> -            pass
> -        elif devspec.startswith("/dev/loop"):
> -            # FIXME: create devices.LoopDevice
> -            log.warning("completely ignoring your loop mount")
> -        elif ":" in devspec and fstype.startswith("nfs"):
> -            # NFS -- preserve but otherwise ignore
> -            device = NFSDevice(devspec,
> -                               fmt=getFormat(fstype,
> -                                                exists=True,
> -                                                device=devspec))
> -        elif devspec.startswith("/") and fstype == "swap":
> -            # swap file
> -            device = FileDevice(devspec,
> -                                parents=get_containing_device(devspec, self.devicetree),
> -                                fmt=getFormat(fstype,
> -                                                 device=devspec,
> -                                                 exists=True),
> -                                exists=True)
> -        elif fstype == "bind" or "bind" in options:
> -            # bind mount... set fstype so later comparison won't
> -            # turn up false positives
> -            fstype = "bind"
> -
> -            # This is probably not going to do anything useful, so we'll
> -            # make sure to try again from FSSet.mountFilesystems. The bind
> -            # mount targets should be accessible by the time we try to do
> -            # the bind mount from there.
> -            parents = get_containing_device(devspec, self.devicetree)
> -            device = DirectoryDevice(devspec, parents=parents, exists=True)
> -            device.format = getFormat("bind",
> -                                      device=device.path,
> -                                      exists=True)
> -        elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts",
> -                            "/sys/fs/selinux", "/proc/bus/usb"):
> -            # drop these now -- we'll recreate later
> -            return None
> -        else:
> -            # nodev filesystem -- preserve or drop completely?
> -            fmt = getFormat(fstype)
> -            fmt_class = get_device_format_class("nodev")
> -            if devspec == "none" or \
> -               (fmt_class and isinstance(fmt, fmt_class)):
> -                device = NoDevice(fmt=fmt)
> -
> -        if device is None:
> -            log.error("failed to resolve %s (%s) from fstab", devspec,
> -                                                              fstype)
> -            raise UnrecognizedFSTabEntryError()
> -
> -        device.setup()
> -        fmt = getFormat(fstype, device=device.path, exists=True)
> -        if fstype != "auto" and None in (device.format.type, fmt.type):
> -            log.info("Unrecognized filesystem type for %s (%s)",
> -                     device.name, fstype)
> -            device.teardown()
> -            raise UnrecognizedFSTabEntryError()
> -
> -        # make sure, if we're using a device from the tree, that
> -        # the device's format we found matches what's in the fstab
> -        ftype = getattr(fmt, "mountType", fmt.type)
> -        dtype = getattr(device.format, "mountType", device.format.type)
> -        if fstype != "auto" and ftype != dtype:
> -            log.info("fstab says %s at %s is %s", dtype, mountpoint, ftype)
> -            if fmt.testMount():
> -                device.format = fmt
> -            else:
> -                device.teardown()
> -                raise FSTabTypeMismatchError("%s: detected as %s, fstab says %s"
> -                                             % (mountpoint, dtype, ftype))
> -        del ftype
> -        del dtype
> -
> -        if hasattr(device.format, "mountpoint"):
> -            device.format.mountpoint = mountpoint
> -
> -        device.format.options = options
> -
> -        return device
> -
> -    def parseFSTab(self, chroot=None):
> -        """ parse /etc/fstab
> -
> -            preconditions:
> -                all storage devices have been scanned, including filesystems
> -            postconditions:
> -
> -            FIXME: control which exceptions we raise
> -
> -            XXX do we care about bind mounts?
> -                how about nodev mounts?
> -                loop mounts?
> -        """
> -        if not chroot or not os.path.isdir(chroot):
> -            chroot = _sysroot
> -
> -        path = "%s/etc/fstab" % chroot
> -        if not os.access(path, os.R_OK):
> -            # XXX should we raise an exception instead?
> -            log.info("cannot open %s for read", path)
> -            return
> -
> -        blkidTab = BlkidTab(chroot=chroot)
> -        try:
> -            blkidTab.parse()
> -            log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> -        except Exception: # pylint: disable=broad-except
> -            log_exception_info(log.info, "error parsing blkid.tab")
> -            blkidTab = None
> -
> -        cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
> -        try:
> -            cryptTab.parse(chroot=chroot)
> -            log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> -        except Exception: # pylint: disable=broad-except
> -            log_exception_info(log.info, "error parsing crypttab")
> -            cryptTab = None
> -
> -        self.blkidTab = blkidTab
> -        self.cryptTab = cryptTab
> -
> -        with open(path) as f:
> -            log.debug("parsing %s", path)
> -
> -            lines = f.readlines()
> -
> -            # save the original file
> -            self.origFStab = ''.join(lines)
> -
> -            for line in lines:
> -
> -                (line, _pound, _comment) = line.partition("#")
> -                fields = line.split()
> -
> -                if not 4 <= len(fields) <= 6:
> -                    continue
> -
> -                try:
> -                    device = self._parseOneLine(*fields)
> -                except UnrecognizedFSTabEntryError:
> -                    # just write the line back out as-is after upgrade
> -                    self.preserveLines.append(line)
> -                    continue
> -
> -                if not device:
> -                    continue
> -
> -                if device not in self.devicetree.devices:
> -                    try:
> -                        self.devicetree._addDevice(device)
> -                    except ValueError:
> -                        # just write duplicates back out post-install
> -                        self.preserveLines.append(line)
> -
> -    def turnOnSwap(self, rootPath=""):
> -        """ Activate the system's swap space. """
> -        if not flags.installer_mode:
> -            return
> -
> -        for device in self.swapDevices:
> -            if isinstance(device, FileDevice):
> -                # set up FileDevices' parents now that they are accessible
> -                targetDir = "%s/%s" % (rootPath, device.path)
> -                parent = get_containing_device(targetDir, self.devicetree)
> -                if not parent:
> -                    log.error("cannot determine which device contains "
> -                              "directory %s", device.path)
> -                    device.parents = []
> -                    self.devicetree._removeDevice(device)
> -                    continue
> -                else:
> -                    device.parents = [parent]
> -
> -            while True:
> -                try:
> -                    device.setup()
> -                    device.format.setup()
> -                except StorageError as e:
> -                    if errorHandler.cb(e) == ERROR_RAISE:
> -                        raise
> -                else:
> -                    break
> -
> -    def mountFilesystems(self, rootPath="", readOnly=None, skipRoot=False):
> -        """ Mount the system's filesystems.
> -
> -            :param str rootPath: the root directory for this filesystem
> -            :param readOnly: read only option str for this filesystem
> -            :type readOnly: str or None
> -            :param bool skipRoot: whether to skip mounting the root filesystem
> -        """
> -        if not flags.installer_mode:
> -            return
> -
> -        devices = list(self.mountpoints.values()) + self.swapDevices
> -        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> -                        self.proc, self.selinux, self.usb, self.run])
> -        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> -
> -        for device in devices:
> -            if not device.format.mountable or not device.format.mountpoint:
> -                continue
> -
> -            if skipRoot and device.format.mountpoint == "/":
> -                continue
> -
> -            options = device.format.options
> -            if "noauto" in options.split(","):
> -                continue
> -
> -            if device.format.type == "bind" and device not in [self.dev, self.run]:
> -                # set up the DirectoryDevice's parents now that they are
> -                # accessible
> -                #
> -                # -- bind formats' device and mountpoint are always both
> -                #    under the chroot. no exceptions. none, damn it.
> -                targetDir = "%s/%s" % (rootPath, device.path)
> -                parent = get_containing_device(targetDir, self.devicetree)
> -                if not parent:
> -                    log.error("cannot determine which device contains "
> -                              "directory %s", device.path)
> -                    device.parents = []
> -                    self.devicetree._removeDevice(device)
> -                    continue
> -                else:
> -                    device.parents = [parent]
> -
> -            try:
> -                device.setup()
> -            except Exception as e: # pylint: disable=broad-except
> -                log_exception_info(fmt_str="unable to set up device %s", fmt_args=[device])
> -                if errorHandler.cb(e) == ERROR_RAISE:
> -                    raise
> -                else:
> -                    continue
> -
> -            if readOnly:
> -                options = "%s,%s" % (options, readOnly)
> -
> -            try:
> -                device.format.setup(options=options,
> -                                    chroot=rootPath)
> -            except Exception as e: # pylint: disable=broad-except
> -                log_exception_info(log.error, "error mounting %s on %s", [device.path, device.format.mountpoint])
> -                if errorHandler.cb(e) == ERROR_RAISE:
> -                    raise
> -
> -        self.active = True
> -
> -    def umountFilesystems(self, swapoff=True):
> -        """ unmount filesystems, except swap if swapoff == False """
> -        devices = list(self.mountpoints.values()) + self.swapDevices
> -        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> -                        self.proc, self.usb, self.selinux, self.run])
> -        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> -        devices.reverse()
> -        for device in devices:
> -            if (not device.format.mountable) or \
> -               (device.format.type == "swap" and not swapoff):
> -                continue
> -
> -            device.format.teardown()
> -            device.teardown()
> -
> -        self.active = False
> -
> -    def createSwapFile(self, device, size):
> -        """ Create and activate a swap file under storage root. """
> -        filename = "/SWAP"
> -        count = 0
> -        basedir = os.path.normpath("%s/%s" % (getTargetPhysicalRoot(),
> -                                              device.format.mountpoint))
> -        while os.path.exists("%s/%s" % (basedir, filename)) or \
> -              self.devicetree.getDeviceByName(filename):
> -            count += 1
> -            filename = "/SWAP-%d" % count
> -
> -        dev = FileDevice(filename,
> -                         size=size,
> -                         parents=[device],
> -                         fmt=getFormat("swap", device=filename))
> -        dev.create()
> -        dev.setup()
> -        dev.format.create()
> -        dev.format.setup()
> -        # nasty, nasty
> -        self.devicetree._addDevice(dev)
> -
> -    def mkDevRoot(self):
> -        root = self.rootDevice
> -        dev = "%s/%s" % (_sysroot, root.path)
> -        if not os.path.exists("%s/dev/root" %(_sysroot,)) and os.path.exists(dev):
> -            rdev = os.stat(dev).st_rdev
> -            os.mknod("%s/dev/root" % (_sysroot,), stat.S_IFBLK | 0o600, rdev)
> -
> -    @property
> -    def swapDevices(self):
> -        swaps = []
> -        for device in self.devices:
> -            if device.format.type == "swap":
> -                swaps.append(device)
> -        return swaps
> -
> -    @property
> -    def rootDevice(self):
> -        for path in ["/", getTargetPhysicalRoot()]:
> -            for device in self.devices:
> -                try:
> -                    mountpoint = device.format.mountpoint
> -                except AttributeError:
> -                    mountpoint = None
> -
> -                if mountpoint == path:
> -                    return device
> -
> -    def write(self):
> -        """ write out all config files based on the set of filesystems """
> -        # /etc/fstab
> -        fstab_path = os.path.normpath("%s/etc/fstab" % _sysroot)
> -        fstab = self.fstab()
> -        open(fstab_path, "w").write(fstab)
> -
> -        # /etc/crypttab
> -        crypttab_path = os.path.normpath("%s/etc/crypttab" % _sysroot)
> -        crypttab = self.crypttab()
> -        origmask = os.umask(0o077)
> -        open(crypttab_path, "w").write(crypttab)
> -        os.umask(origmask)
> -
> -        # /etc/mdadm.conf
> -        mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % _sysroot)
> -        mdadm_conf = self.mdadmConf()
> -        if mdadm_conf:
> -            open(mdadm_path, "w").write(mdadm_conf)
> -
> -        # /etc/multipath.conf
> -        if self.devicetree.getDevicesByType("dm-multipath"):
> -            util.copy_to_system("/etc/multipath.conf")
> -            util.copy_to_system("/etc/multipath/wwids")
> -            util.copy_to_system("/etc/multipath/bindings")
> -        else:
> -            log.info("not writing out mpath configuration")
> -
> -    def crypttab(self):
> -        # if we are upgrading, do we want to update crypttab?
> -        # gut reaction says no, but plymouth needs the names to be very
> -        # specific for passphrase prompting
> -        if not self.cryptTab:
> -            self.cryptTab = CryptTab(self.devicetree)
> -            self.cryptTab.populate()
> -
> -        devices = list(self.mountpoints.values()) + self.swapDevices
> -
> -        # prune crypttab -- only mappings required by one or more entries
> -        for name in self.cryptTab.mappings.keys():
> -            keep = False
> -            mapInfo = self.cryptTab[name]
> -            cryptoDev = mapInfo['device']
> -            for device in devices:
> -                if device == cryptoDev or device.dependsOn(cryptoDev):
> -                    keep = True
> -                    break
> -
> -            if not keep:
> -                del self.cryptTab.mappings[name]
> -
> -        return self.cryptTab.crypttab()
> -
> -    def mdadmConf(self):
> -        """ Return the contents of mdadm.conf. """
> -        arrays = self.devicetree.getDevicesByType("mdarray")
> -        arrays.extend(self.devicetree.getDevicesByType("mdbiosraidarray"))
> -        arrays.extend(self.devicetree.getDevicesByType("mdcontainer"))
> -        # Sort it, this not only looks nicer, but this will also put
> -        # containers (which get md0, md1, etc.) before their members
> -        # (which get md127, md126, etc.). and lame as it is mdadm will not
> -        # assemble the whole stack in one go unless listed in the proper order
> -        # in mdadm.conf
> -        arrays.sort(key=lambda d: d.path)
> -        if not arrays:
> -            return ""
> -
> -        conf = "# mdadm.conf written out by anaconda\n"
> -        conf += "MAILADDR root\n"
> -        conf += "AUTO +imsm +1.x -all\n"
> -        devices = list(self.mountpoints.values()) + self.swapDevices
> -        for array in arrays:
> -            for device in devices:
> -                if device == array or device.dependsOn(array):
> -                    conf += array.mdadmConfEntry
> -                    break
> -
> -        return conf
> -
> -    def fstab (self):
> -        fmt_str = "%-23s %-23s %-7s %-15s %d %d\n"
> -        fstab = """
> -#
> -# /etc/fstab
> -# Created by anaconda on %s
> -#
> -# Accessible filesystems, by reference, are maintained under '/dev/disk'
> -# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
> -#
> -""" % time.asctime()
> -
> -        devices = sorted(self.mountpoints.values(),
> -                         key=lambda d: d.format.mountpoint)
> -
> -        # filter swaps only in installer mode
> -        if flags.installer_mode:
> -            devices += [dev for dev in self.swapDevices
> -                        if dev in self._fstab_swaps]
> -        else:
> -            devices += self.swapDevices
> -
> -        netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
> -
> -        rootdev = devices[0]
> -        root_on_netdev = any(rootdev.dependsOn(netdev) for netdev in netdevs)
> -
> -        for device in devices:
> -            # why the hell do we put swap in the fstab, anyway?
> -            if not device.format.mountable and device.format.type != "swap":
> -                continue
> -
> -            # Don't write out lines for optical devices, either.
> -            if isinstance(device, OpticalDevice):
> -                continue
> -
> -            fstype = getattr(device.format, "mountType", device.format.type)
> -            if fstype == "swap":
> -                mountpoint = "swap"
> -                options = device.format.options
> -            else:
> -                mountpoint = device.format.mountpoint
> -                options = device.format.options
> -                if not mountpoint:
> -                    log.warning("%s filesystem on %s has no mountpoint",
> -                                                            fstype,
> -                                                            device.path)
> -                    continue
> -
> -            options = options or "defaults"
> -            for netdev in netdevs:
> -                if device.dependsOn(netdev):
> -                    if root_on_netdev and mountpoint not in ["/", "/usr"]:
> -                        options = options + ",x-initrd.mount"
> -                    break
> -            if device.encrypted:
> -                options += ",x-systemd.device-timeout=0"
> -            devspec = device.fstabSpec
> -            dump = device.format.dump
> -            if device.format.check and mountpoint == "/":
> -                passno = 1
> -            elif device.format.check:
> -                passno = 2
> -            else:
> -                passno = 0
> -            fstab = fstab + device.fstabComment
> -            fstab = fstab + fmt_str % (devspec, mountpoint, fstype,
> -                                       options, dump, passno)
> -
> -        # now, write out any lines we were unable to process because of
> -        # unrecognized filesystems or unresolveable device specifications
> -        for line in self.preserveLines:
> -            fstab += line
> -
> -        return fstab
> -
> -    def addFstabSwap(self, device):
> -        """
> -        Add swap device to the list of swaps that should appear in the fstab.
> -
> -        :param device: swap device that should be added to the list
> -        :type device: blivet.devices.StorageDevice instance holding a swap format
> -
> -        """
> -
> -        self._fstab_swaps.add(device)
> -
> -    def removeFstabSwap(self, device):
> -        """
> -        Remove swap device from the list of swaps that should appear in the fstab.
> -
> -        :param device: swap device that should be removed from the list
> -        :type device: blivet.devices.StorageDevice instance holding a swap format
> -
> -        """
> -
> -        try:
> -            self._fstab_swaps.remove(device)
> -        except KeyError:
> -            pass
> -
> -    def setFstabSwaps(self, devices):
> -        """
> -        Set swap devices that should appear in the fstab.
> -
> -        :param devices: iterable providing devices that should appear in the fstab
> -        :type devices: iterable providing blivet.devices.StorageDevice instances holding
> -                       a swap format
> -
> -        """
> -
> -        self._fstab_swaps = set(devices)
> -
> -def releaseFromRedhatRelease(fn):
> -    """
> -    Attempt to identify the installation of a Linux distribution via
> -    /etc/redhat-release.  This file must already have been verified to exist
> -    and be readable.
> -
> -    :param fn: an open filehandle on /etc/redhat-release
> -    :type fn: filehandle
> -    :returns: The distribution's name and version, or None for either or both
> -    if they cannot be determined
> -    :rtype: (string, string)
> -    """
> -    relName = None
> -    relVer = None
> -
> -    with open(fn) as f:
> -        try:
> -            relstr = f.readline().strip()
> -        except (IOError, AttributeError):
> -            relstr = ""
> -
> -    # get the release name and version
> -    # assumes that form is something
> -    # like "Red Hat Linux release 6.2 (Zoot)"
> -    (product, sep, version) = relstr.partition(" release ")
> -    if sep:
> -        relName = product
> -        relVer = version.split()[0]
> -
> -    return (relName, relVer)
> -
> -def releaseFromOsRelease(fn):
> -    """
> -    Attempt to identify the installation of a Linux distribution via
> -    /etc/os-release.  This file must already have been verified to exist
> -    and be readable.
> -
> -    :param fn: an open filehandle on /etc/os-release
> -    :type fn: filehandle
> -    :returns: The distribution's name and version, or None for either or both
> -    if they cannot be determined
> -    :rtype: (string, string)
> -    """
> -    relName = None
> -    relVer = None
> -
> -    with open(fn, "r") as f:
> -        parser = shlex.shlex(f)
> -
> -        while True:
> -            key = parser.get_token()
> -            if key == parser.eof:
> -                break
> -            elif key == "NAME":
> -                # Throw away the "=".
> -                parser.get_token()
> -                relName = parser.get_token().strip("'\"")
> -            elif key == "VERSION_ID":
> -                # Throw away the "=".
> -                parser.get_token()
> -                relVer = parser.get_token().strip("'\"")
> -
> -    return (relName, relVer)
> -
> -def getReleaseString():
> -    """
> -    Attempt to identify the installation of a Linux distribution by checking
> -    a previously mounted filesystem for several files.  The filesystem must
> -    be mounted under the target physical root.
> -
> -    :returns: The machine's arch, distribution name, and distribution version
> -    or None for any parts that cannot be determined
> -    :rtype: (string, string, string)
> -    """
> -    relName = None
> -    relVer = None
> -
> -    try:
> -        relArch = util.capture_output(["arch"], root=_sysroot).strip()
> -    except OSError:
> -        relArch = None
> -
> -    filename = "%s/etc/redhat-release" % getSysroot()
> -    if os.access(filename, os.R_OK):
> -        (relName, relVer) = releaseFromRedhatRelease(filename)
> -    else:
> -        filename = "%s/etc/os-release" % getSysroot()
> -        if os.access(filename, os.R_OK):
> -            (relName, relVer) = releaseFromOsRelease(filename)
> -
> -    return (relArch, relName, relVer)
> -
> -def findExistingInstallations(devicetree):
> -    if not os.path.exists(getTargetPhysicalRoot()):
> -        util.makedirs(getTargetPhysicalRoot())
> -
> -    roots = []
> -    for device in devicetree.leaves:
> -        if not device.format.linuxNative or not device.format.mountable or \
> -           not device.controllable:
> -            continue
> -
> -        try:
> -            device.setup()
> -        except Exception: # pylint: disable=broad-except
> -            log_exception_info(log.warning, "setup of %s failed", [device.name])
> -            continue
> -
> -        options = device.format.options + ",ro"
> -        try:
> -            device.format.mount(options=options, mountpoint=getSysroot())
> -        except Exception: # pylint: disable=broad-except
> -            log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
> -            device.teardown()
> -            continue
> -
> -        if not os.access(getSysroot() + "/etc/fstab", os.R_OK):
> -            device.teardown(recursive=True)
> -            continue
> -
> -        try:
> -            (architecture, product, version) = getReleaseString()
> -        except ValueError:
> -            name = _("Linux on %s") % device.name
> -        else:
> -            # I'd like to make this finer grained, but it'd be very difficult
> -            # to translate.
> -            if not product or not version or not architecture:
> -                name = _("Unknown Linux")
> -            elif "linux" in product.lower():
> -                name = _("%(product)s %(version)s for %(arch)s") % \
> -                        {"product": product, "version": version, "arch": architecture}
> -            else:
> -                name = _("%(product)s Linux %(version)s for %(arch)s") % \
> -                        {"product": product, "version": version, "arch": architecture}
> -
> -        (mounts, swaps) = parseFSTab(devicetree, chroot=_sysroot)
> -        device.teardown()
> -        if not mounts and not swaps:
> -            # empty /etc/fstab. weird, but I've seen it happen.
> -            continue
> -        roots.append(Root(mounts=mounts, swaps=swaps, name=name))
> -
> -    return roots
> -
> -class Root(object):
> -    """ A Root represents an existing OS installation. """
> -    def __init__(self, mounts=None, swaps=None, name=None):
> -        """
> -            :keyword mounts: mountpoint dict
> -            :type mounts: dict (mountpoint keys and :class:`~.devices.StorageDevice` values)
> -            :keyword swaps: swap device list
> -            :type swaps: list of :class:`~.devices.StorageDevice`
> -            :keyword name: name for this installed OS
> -            :type name: str
> -        """
> -        # mountpoint key, StorageDevice value
> -        if not mounts:
> -            self.mounts = {}
> -        else:
> -            self.mounts = mounts
> -
> -        # StorageDevice
> -        if not swaps:
> -            self.swaps = []
> -        else:
> -            self.swaps = swaps
> -
> -        self.name = name    # eg: "Fedora Linux 16 for x86_64", "Linux on sda2"
> -
> -        if not self.name and "/" in self.mounts:
> -            self.name = self.mounts["/"].format.uuid
> -
> -    @property
> -    def device(self):
> -        return self.mounts.get("/")
> -
> -def parseFSTab(devicetree, chroot=None):
> -    """ parse /etc/fstab and return a tuple of a mount dict and swap list """
> -    if not chroot or not os.path.isdir(chroot):
> -        chroot = _sysroot
> -
> -    mounts = {}
> -    swaps = []
> -    path = "%s/etc/fstab" % chroot
> -    if not os.access(path, os.R_OK):
> -        # XXX should we raise an exception instead?
> -        log.info("cannot open %s for read", path)
> -        return (mounts, swaps)
> -
> -    blkidTab = BlkidTab(chroot=chroot)
> -    try:
> -        blkidTab.parse()
> -        log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> -    except Exception: # pylint: disable=broad-except
> -        log_exception_info(log.info, "error parsing blkid.tab")
> -        blkidTab = None
> -
> -    cryptTab = CryptTab(devicetree, blkidTab=blkidTab, chroot=chroot)
> -    try:
> -        cryptTab.parse(chroot=chroot)
> -        log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> -    except Exception: # pylint: disable=broad-except
> -        log_exception_info(log.info, "error parsing crypttab")
> -        cryptTab = None
> -
> -    with open(path) as f:
> -        log.debug("parsing %s", path)
> -        for line in f.readlines():
> -
> -            (line, _pound, _comment) = line.partition("#")
> -            fields = line.split(None, 4)
> -
> -            if len(fields) < 5:
> -                continue
> -
> -            (devspec, mountpoint, fstype, options, _rest) = fields
> -
> -            # find device in the tree
> -            device = devicetree.resolveDevice(devspec,
> -                                              cryptTab=cryptTab,
> -                                              blkidTab=blkidTab,
> -                                              options=options)
> -
> -            if device is None:
> -                continue
> -
> -            if fstype != "swap":
> -                mounts[mountpoint] = device
> -            else:
> -                swaps.append(device)
> -
> -    return (mounts, swaps)
> diff --git a/blivet/autopart.py b/blivet/autopart.py
> new file mode 100644
> index 0000000..f35cc88
> --- /dev/null
> +++ b/blivet/autopart.py
> @@ -0,0 +1,483 @@
> +#
> +# Copyright (C) 2009-2015  Red Hat, Inc.
> +#
> +# This copyrighted material is made available to anyone wishing to use,
> +# modify, copy, or redistribute it subject to the terms and conditions of
> +# the GNU General Public License v.2, or (at your option) any later version.
> +# This program is distributed in the hope that it will be useful, but WITHOUT
> +# ANY WARRANTY expressed or implied, including the implied warranties of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
> +# Public License for more details.  You should have received a copy of the
> +# GNU General Public License along with this program; if not, write to the
> +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> +# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
> +# source code or documentation are not subject to the GNU General Public
> +# License and may only be used or replicated with the express permission of
> +# Red Hat, Inc.
> +#
> +# Red Hat Author(s): Dave Lehman <dlehman at redhat.com>
> +#
> +
> +"""This module provides functions related to autopartitioning."""
> +
> +import parted
> +from decimal import Decimal
> +
> +from . import util
> +from .size import Size
> +from .devices.partition import PartitionDevice, FALLBACK_DEFAULT_PART_SIZE
> +from .devices.luks import LUKSDevice
> +from .errors import NoDisksError, NotEnoughFreeSpaceError
> +from .formats import getFormat
> +from .partitioning import doPartitioning, getFreeRegions, growLVM
> +from . import _
> +
> +from pykickstart.constants import AUTOPART_TYPE_BTRFS, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP, AUTOPART_TYPE_PLAIN
> +
> +import logging
> +log = logging.getLogger("anaconda")
> +
> +# maximum ratio of swap size to disk size (10 %)
> +MAX_SWAP_DISK_RATIO = Decimal('0.1')
> +
> +def swapSuggestion(quiet=False, hibernation=False, disk_space=None):
> +    """
> +    Suggest the size of the swap partition that will be created.
> +
> +    :param quiet: whether to log size information or not
> +    :type quiet: bool
> +    :param hibernation: calculate swap size big enough for hibernation
> +    :type hibernation: bool
> +    :param disk_space: how much disk space is available
> +    :type disk_space: :class:`~.size.Size`
> +    :return: calculated swap size
> +
> +    """
> +
> +    mem = util.total_memory()
> +    mem = ((mem / 16) + 1) * 16
> +    if not quiet:
> +        log.info("Detected %s of memory", mem)
> +
> +    sixtyfour_GiB = Size("64 GiB")
> +
> +    # the succeeding if-statement implements the following formula for
> +    # suggested swap size.
> +    #
> +    # swap(mem) = 2 * mem, if mem < 2 GiB
> +    #           = mem,     if 2 GiB <= mem < 8 GiB
> +    #           = mem / 2, if 8 GIB <= mem < 64 GiB
> +    #           = 4 GiB,   if mem >= 64 GiB
> +    if mem < Size("2 GiB"):
> +        swap = 2 * mem
> +
> +    elif mem < Size("8 GiB"):
> +        swap = mem
> +
> +    elif mem < sixtyfour_GiB:
> +        swap = mem / 2
> +
> +    else:
> +        swap = Size("4 GiB")
> +
> +    if hibernation:
> +        if mem <= sixtyfour_GiB:
> +            swap = mem + swap
> +        else:
> +            log.info("Ignoring --hibernation option on systems with %s of RAM or more", sixtyfour_GiB)
> +
> +    if disk_space is not None and not hibernation:
> +        max_swap = disk_space * MAX_SWAP_DISK_RATIO
> +        if swap > max_swap:
> +            log.info("Suggested swap size (%(swap)s) exceeds %(percent)d %% of "
> +                     "disk space, using %(percent)d %% of disk space (%(size)s) "
> +                     "instead.", {"percent": MAX_SWAP_DISK_RATIO*100,
> +                                  "swap": swap,
> +                                  "size": max_swap})
> +            swap = max_swap
> +
> +    if not quiet:
> +        log.info("Swap attempt of %s", swap)
> +
> +    return swap
> +
> +def _getCandidateDisks(storage):
> +    """ Return a list of disks to be used for autopart.
> +
> +        Disks must be partitioned and have a single free region large enough
> +        for a default-sized (500MiB) partition. They must also be in
> +        :attr:`StorageDiscoveryConfig.clearPartDisks` if it is non-empty.
> +
> +        :param storage: a Blivet instance
> +        :type storage: :class:`~.Blivet`
> +        :returns: a list of partitioned disks with at least 500MiB of free space
> +        :rtype: list of :class:`~.devices.StorageDevice`
> +    """
> +    disks = []
> +    for disk in storage.partitioned:
> +        if storage.config.clearPartDisks and \
> +           (disk.name not in storage.config.clearPartDisks):
> +            continue
> +
> +        part = disk.format.firstPartition
> +        while part:
> +            if not part.type & parted.PARTITION_FREESPACE:
> +                part = part.nextPartition()
> +                continue
> +
> +            if Size(part.getLength(unit="B")) > PartitionDevice.defaultSize:
> +                disks.append(disk)
> +                break
> +
> +            part = part.nextPartition()
> +
> +    return disks
> +
> +def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> +    """ Schedule creation of a lvm/btrfs member partitions for autopart.
> +
> +        We create one such partition on each disk. They are not allocated until
> +        later (in :func:`doPartitioning`).
> +
> +        :param storage: a :class:`~.Blivet` instance
> +        :type storage: :class:`~.Blivet`
> +        :param disks: list of partitioned disks with free space
> +        :type disks: list of :class:`~.devices.StorageDevice`
> +        :param min_luks_entropy: minimum entropy in bits required for
> +                                 luks format creation
> +        :type min_luks_entropy: int
> +        :returns: list of newly created (unallocated) partitions
> +        :rtype: list of :class:`~.devices.PartitionDevice`
> +    """
> +    # create a separate pv or btrfs partition for each disk with free space
> +    devs = []
> +
> +    # only schedule the partitions if either lvm or btrfs autopart was chosen
> +    if storage.autoPartType == AUTOPART_TYPE_PLAIN:
> +        return devs
> +
> +    for disk in disks:
> +        if storage.encryptedAutoPart:
> +            fmt_type = "luks"
> +            fmt_args = {"passphrase": storage.encryptionPassphrase,
> +                        "cipher": storage.encryptionCipher,
> +                        "escrow_cert": storage.autoPartEscrowCert,
> +                        "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> +                        "min_luks_entropy": min_luks_entropy}
> +        else:
> +            if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> +                fmt_type = "lvmpv"
> +            else:
> +                fmt_type = "btrfs"
> +            fmt_args = {}
> +        part = storage.newPartition(fmt_type=fmt_type,
> +                                                fmt_args=fmt_args,
> +                                                grow=True,
> +                                                parents=[disk])
> +        storage.createDevice(part)
> +        devs.append(part)
> +
> +    return devs
> +
> +def _schedulePartitions(storage, disks, implicit_devices, min_luks_entropy=0):
> +    """ Schedule creation of autopart partitions.
> +
> +        This only schedules the requests for actual partitions.
> +
> +        :param storage: a :class:`~.Blivet` instance
> +        :type storage: :class:`~.Blivet`
> +        :param disks: list of partitioned disks with free space
> +        :type disks: list of :class:`~.devices.StorageDevice`
> +        :param min_luks_entropy: minimum entropy in bits required for
> +                                 luks format creation
> +        :type min_luks_entropy: int
> +        :returns: None
> +        :rtype: None
> +    """
> +    # basis for requests with requiredSpace is the sum of the sizes of the
> +    # two largest free regions
> +    all_free = (Size(reg.getLength(unit="B")) for reg in getFreeRegions(disks))
> +    all_free = sorted(all_free, reverse=True)
> +    if not all_free:
> +        # this should never happen since we've already filtered the disks
> +        # to those with at least 500MiB free
> +        log.error("no free space on disks %s", [d.name for d in disks])
> +        return
> +
> +    free = all_free[0]
> +    if len(all_free) > 1:
> +        free += all_free[1]
> +
> +    # The boot disk must be set at this point. See if any platform-specific
> +    # stage1 device we might allocate already exists on the boot disk.
> +    stage1_device = None
> +    for device in storage.devices:
> +        if storage.bootloader.stage1_disk not in device.disks:
> +            continue
> +
> +        if storage.bootloader.is_valid_stage1_device(device, early=True):
> +            stage1_device = device
> +            break
> +
> +    #
> +    # First pass is for partitions only. We'll do LVs later.
> +    #
> +    for request in storage.autoPartitionRequests:
> +        if ((request.lv and
> +             storage.autoPartType in (AUTOPART_TYPE_LVM,
> +                                      AUTOPART_TYPE_LVM_THINP)) or
> +            (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS)):
> +            continue
> +
> +        if request.requiredSpace and request.requiredSpace > free:
> +            continue
> +
> +        elif request.fstype in ("prepboot", "efi", "macefi", "hfs+") and \
> +             (storage.bootloader.skip_bootloader or stage1_device):
> +            # there should never be a need for more than one of these
> +            # partitions, so skip them.
> +            log.info("skipping unneeded stage1 %s request", request.fstype)
> +            log.debug("%s", request)
> +
> +            if request.fstype in ["efi", "macefi"] and stage1_device:
> +                # Set the mountpoint for the existing EFI boot partition
> +                stage1_device.format.mountpoint = "/boot/efi"
> +
> +            log.debug("%s", stage1_device)
> +            continue
> +        elif request.fstype == "biosboot":
> +            is_gpt = (stage1_device and
> +                      getattr(stage1_device.format, "labelType", None) == "gpt")
> +            has_bios_boot = (stage1_device and
> +                             any([p.format.type == "biosboot"
> +                                    for p in storage.partitions
> +                                        if p.disk == stage1_device]))
> +            if (storage.bootloader.skip_bootloader or
> +                not (stage1_device and stage1_device.isDisk and
> +                    is_gpt and not has_bios_boot)):
> +                # there should never be a need for more than one of these
> +                # partitions, so skip them.
> +                log.info("skipping unneeded stage1 %s request", request.fstype)
> +                log.debug("%s", request)
> +                log.debug("%s", stage1_device)
> +                continue
> +
> +        if request.size > all_free[0]:
> +            # no big enough free space for the requested partition
> +            raise NotEnoughFreeSpaceError(_("No big enough free space on disks for "
> +                                            "automatic partitioning"))
> +
> +        if request.encrypted and storage.encryptedAutoPart:
> +            fmt_type = "luks"
> +            fmt_args = {"passphrase": storage.encryptionPassphrase,
> +                        "cipher": storage.encryptionCipher,
> +                        "escrow_cert": storage.autoPartEscrowCert,
> +                        "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> +                        "min_luks_entropy": min_luks_entropy}
> +        else:
> +            fmt_type = request.fstype
> +            fmt_args = {}
> +
> +        dev = storage.newPartition(fmt_type=fmt_type,
> +                                            fmt_args=fmt_args,
> +                                            size=request.size,
> +                                            grow=request.grow,
> +                                            maxsize=request.maxSize,
> +                                            mountpoint=request.mountpoint,
> +                                            parents=disks,
> +                                            weight=request.weight)
> +
> +        # schedule the device for creation
> +        storage.createDevice(dev)
> +
> +        if request.encrypted and storage.encryptedAutoPart:
> +            luks_fmt = getFormat(request.fstype,
> +                                 device=dev.path,
> +                                 mountpoint=request.mountpoint)
> +            luks_dev = LUKSDevice("luks-%s" % dev.name,
> +                                  fmt=luks_fmt,
> +                                  size=dev.size,
> +                                  parents=dev)
> +            storage.createDevice(luks_dev)
> +
> +        if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP,
> +                                    AUTOPART_TYPE_BTRFS):
> +            # doing LVM/BTRFS -- make sure the newly created partition fits in some
> +            # free space together with one of the implicitly requested partitions
> +            smallest_implicit = sorted(implicit_devices, key=lambda d: d.size)[0]
> +            if (request.size + smallest_implicit.size) > all_free[0]:
> +                # not enough space to allocate the smallest implicit partition
> +                # and the request, make the implicit partitions smaller in
> +                # attempt to make space for the request
> +                for implicit_req in implicit_devices:
> +                    implicit_req.size = FALLBACK_DEFAULT_PART_SIZE
> +
> +    return implicit_devices
> +
> +def _scheduleVolumes(storage, devs):
> +    """ Schedule creation of autopart lvm/btrfs volumes.
> +
> +        Schedules encryption of member devices if requested, schedules creation
> +        of the container (:class:`~.devices.LVMVolumeGroupDevice` or
> +        :class:`~.devices.BTRFSVolumeDevice`) then schedules creation of the
> +        autopart volume requests.
> +
> +        :param storage: a :class:`~.Blivet` instance
> +        :type storage: :class:`~.Blivet`
> +        :param devs: list of member partitions
> +        :type devs: list of :class:`~.devices.PartitionDevice`
> +        :returns: None
> +        :rtype: None
> +
> +        If an appropriate bootloader stage1 device exists on the boot drive, any
> +        autopart request to create another one will be skipped/discarded.
> +    """
> +    if not devs:
> +        return
> +
> +    if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> +        new_container = storage.newVG
> +        new_volume = storage.newLV
> +        format_name = "lvmpv"
> +    else:
> +        new_container = storage.newBTRFS
> +        new_volume = storage.newBTRFS
> +        format_name = "btrfs"
> +
> +    if storage.encryptedAutoPart:
> +        pvs = []
> +        for dev in devs:
> +            pv = LUKSDevice("luks-%s" % dev.name,
> +                            fmt=getFormat(format_name, device=dev.path),
> +                            size=dev.size,
> +                            parents=dev)
> +            pvs.append(pv)
> +            storage.createDevice(pv)
> +    else:
> +        pvs = devs
> +
> +    # create a vg containing all of the autopart pvs
> +    container = new_container(parents=pvs)
> +    storage.createDevice(container)
> +
> +    #
> +    # Convert storage.autoPartitionRequests into Device instances and
> +    # schedule them for creation.
> +    #
> +    # Second pass, for LVs only.
> +    pool = None
> +    for request in storage.autoPartitionRequests:
> +        btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
> +        lv = (storage.autoPartType in (AUTOPART_TYPE_LVM,
> +                                       AUTOPART_TYPE_LVM_THINP) and request.lv)
> +        thinlv = (storage.autoPartType == AUTOPART_TYPE_LVM_THINP and
> +                  request.lv and request.thin)
> +        if thinlv and pool is None:
> +            # create a single thin pool in the vg
> +            pool = storage.newLV(parents=[container], thin_pool=True, grow=True)
> +            storage.createDevice(pool)
> +
> +        if not btr and not lv and not thinlv:
> +            continue
> +
> +        # required space isn't relevant on btrfs
> +        if (lv or thinlv) and \
> +           request.requiredSpace and request.requiredSpace > container.size:
> +            continue
> +
> +        if request.fstype is None:
> +            if btr:
> +                # btrfs volumes can only contain btrfs filesystems
> +                request.fstype = "btrfs"
> +            else:
> +                request.fstype = storage.defaultFSType
> +
> +        kwargs = {"mountpoint": request.mountpoint,
> +                  "fmt_type": request.fstype}
> +        if lv or thinlv:
> +            if thinlv:
> +                parents = [pool]
> +            else:
> +                parents = [container]
> +
> +            kwargs.update({"parents": parents,
> +                           "grow": request.grow,
> +                           "maxsize": request.maxSize,
> +                           "size": request.size,
> +                           "thin_volume": thinlv})
> +        else:
> +            kwargs.update({"parents": [container],
> +                           "size": request.size,
> +                           "subvol": True})
> +
> +        dev = new_volume(**kwargs)
> +
> +        # schedule the device for creation
> +        storage.createDevice(dev)
> +
> +def doAutoPartition(storage, data, min_luks_entropy=0):
> +    """ Perform automatic partitioning.
> +
> +        :param storage: a :class:`~.Blivet` instance
> +        :type storage: :class:`~.Blivet`
> +        :param data: kickstart data
> +        :type data: :class:`pykickstart.BaseHandler`
> +        :param min_luks_entropy: minimum entropy in bits required for
> +                                 luks format creation
> +        :type min_luks_entropy: int
> +
> +        :attr:`Blivet.doAutoPart` controls whether this method creates the
> +        automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> +        which variant of autopart used. It uses one of the pykickstart
> +        AUTOPART_TYPE_* constants. The set of eligible disks is defined in
> +        :attr:`StorageDiscoveryConfig.clearPartDisks`.
> +
> +        .. note::
> +
> +            Clearing of partitions is handled separately, in
> +            :meth:`~.Blivet.clearPartitions`.
> +    """
> +    # pylint: disable=unused-argument
> +    log.debug("doAutoPart: %s", storage.doAutoPart)
> +    log.debug("encryptedAutoPart: %s", storage.encryptedAutoPart)
> +    log.debug("autoPartType: %s", storage.autoPartType)
> +    log.debug("clearPartType: %s", storage.config.clearPartType)
> +    log.debug("clearPartDisks: %s", storage.config.clearPartDisks)
> +    log.debug("autoPartitionRequests:\n%s", "".join([str(p) for p in storage.autoPartitionRequests]))
> +    log.debug("storage.disks: %s", [d.name for d in storage.disks])
> +    log.debug("storage.partitioned: %s", [d.name for d in storage.partitioned])
> +    log.debug("all names: %s", [d.name for d in storage.devices])
> +    log.debug("boot disk: %s", getattr(storage.bootDisk, "name", None))
> +
> +    disks = []
> +    devs = []
> +
> +    if not storage.doAutoPart:
> +        return
> +
> +    if not storage.partitioned:
> +        raise NoDisksError(_("No usable disks selected"))
> +
> +    disks = _getCandidateDisks(storage)
> +    devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> +    log.debug("candidate disks: %s", disks)
> +    log.debug("devs: %s", devs)
> +
> +    if disks == []:
> +        raise NotEnoughFreeSpaceError(_("Not enough free space on disks for "
> +                                      "automatic partitioning"))
> +
> +    devs = _schedulePartitions(storage, disks, devs, min_luks_entropy=min_luks_entropy)
> +
> +    # run the autopart function to allocate and grow partitions
> +    doPartitioning(storage)
> +    _scheduleVolumes(storage, devs)
> +
> +    # grow LVs
> +    growLVM(storage)
> +
> +    storage.setUpBootLoader()
> +
> +    # only newly added swaps should appear in the fstab
> +    new_swaps = (dev for dev in storage.swaps if not dev.format.exists)
> +    storage.setFstabSwaps(new_swaps)
> diff --git a/blivet/devicelibs/swap.py b/blivet/devicelibs/swap.py
> index 63e0dec9..5314810 100644
> --- a/blivet/devicelibs/swap.py
> +++ b/blivet/devicelibs/swap.py
> @@ -22,19 +22,14 @@
>
>   import resource
>   import os
> -from decimal import Decimal
>
>   from ..errors import DMError, OldSwapError, SuspendError, SwapError, UnknownSwapError
>   from .. import util
>   from . import dm
> -from ..size import Size
>
>   import logging
>   log = logging.getLogger("blivet")
>
> -# maximum ratio of swap size to disk size (10 %)
> -MAX_SWAP_DISK_RATIO = Decimal('0.1')
> -
>   def mkswap(device, label=None):
>       # We use -f to force since mkswap tends to refuse creation on lvs with
>       # a message about erasing bootbits sectors on whole disks. Bah.
> @@ -117,65 +112,3 @@ def swapstatus(device):
>               break
>
>       return status
> -
> -def swapSuggestion(quiet=False, hibernation=False, disk_space=None):
> -    """
> -    Suggest the size of the swap partition that will be created.
> -
> -    :param quiet: whether to log size information or not
> -    :type quiet: bool
> -    :param hibernation: calculate swap size big enough for hibernation
> -    :type hibernation: bool
> -    :param disk_space: how much disk space is available
> -    :type disk_space: :class:`~.size.Size`
> -    :return: calculated swap size
> -
> -    """
> -
> -    mem = util.total_memory()
> -    mem = ((mem / 16) + 1) * 16
> -    if not quiet:
> -        log.info("Detected %s of memory", mem)
> -
> -    sixtyfour_GiB = Size("64 GiB")
> -
> -    # the succeeding if-statement implements the following formula for
> -    # suggested swap size.
> -    #
> -    # swap(mem) = 2 * mem, if mem < 2 GiB
> -    #           = mem,     if 2 GiB <= mem < 8 GiB
> -    #           = mem / 2, if 8 GIB <= mem < 64 GiB
> -    #           = 4 GiB,   if mem >= 64 GiB
> -    if mem < Size("2 GiB"):
> -        swap = 2 * mem
> -
> -    elif mem < Size("8 GiB"):
> -        swap = mem
> -
> -    elif mem < sixtyfour_GiB:
> -        swap = mem / 2
> -
> -    else:
> -        swap = Size("4 GiB")
> -
> -    if hibernation:
> -        if mem <= sixtyfour_GiB:
> -            swap = mem + swap
> -        else:
> -            log.info("Ignoring --hibernation option on systems with %s of RAM or more", sixtyfour_GiB)
> -
> -    if disk_space is not None and not hibernation:
> -        max_swap = disk_space * MAX_SWAP_DISK_RATIO
> -        if swap > max_swap:
> -            log.info("Suggested swap size (%(swap)s) exceeds %(percent)d %% of "
> -                     "disk space, using %(percent)d %% of disk space (%(size)s) "
> -                     "instead.", {"percent": MAX_SWAP_DISK_RATIO*100,
> -                                  "swap": swap,
> -                                  "size": max_swap})
> -            swap = max_swap
> -
> -    if not quiet:
> -        log.info("Swap attempt of %s", swap)
> -
> -    return swap
> -
> diff --git a/blivet/osinstall.py b/blivet/osinstall.py
> new file mode 100644
> index 0000000..3729246
> --- /dev/null
> +++ b/blivet/osinstall.py
> @@ -0,0 +1,1107 @@
> +#
> +# Copyright (C) 2009-2015  Red Hat, Inc.
> +#
> +# This copyrighted material is made available to anyone wishing to use,
> +# modify, copy, or redistribute it subject to the terms and conditions of
> +# the GNU General Public License v.2, or (at your option) any later version.
> +# This program is distributed in the hope that it will be useful, but WITHOUT
> +# ANY WARRANTY expressed or implied, including the implied warranties of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
> +# Public License for more details.  You should have received a copy of the
> +# GNU General Public License along with this program; if not, write to the
> +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> +# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
> +# source code or documentation are not subject to the GNU General Public
> +# License and may only be used or replicated with the express permission of
> +# Red Hat, Inc.
> +#
> +# Red Hat Author(s): Dave Lehman <dlehman at redhat.com>
> +#
> +
> +"""This module provides functions related to OS installation."""
> +
> +import shlex
> +import os
> +import stat
> +import time
> +
> +from . import util
> +from . import getSysroot, getTargetPhysicalRoot, errorHandler, ERROR_RAISE
> +
> +from .storage_log import log_exception_info
> +from .devices import FileDevice, NFSDevice, NoDevice, OpticalDevice, NetworkStorageDevice, DirectoryDevice
> +from .errors import FSTabTypeMismatchError, UnrecognizedFSTabEntryError, StorageError, FSResizeError
> +from .formats import get_device_format_class
> +from .devicelibs.dm import name_from_dm_node
> +from .devicelibs.crypto import generateBackupPassphrase
> +from .formats import getFormat
> +from .flags import flags
> +
> +from .i18n import _
> +
> +import logging
> +log = logging.getLogger("blivet")
> +
> +try:
> +    import nss.nss
> +except ImportError:
> +    nss = None
> +
> +def releaseFromRedhatRelease(fn):
> +    """
> +    Attempt to identify the installation of a Linux distribution via
> +    /etc/redhat-release.  This file must already have been verified to exist
> +    and be readable.
> +
> +    :param fn: an open filehandle on /etc/redhat-release
> +    :type fn: filehandle
> +    :returns: The distribution's name and version, or None for either or both
> +    if they cannot be determined
> +    :rtype: (string, string)
> +    """
> +    relName = None
> +    relVer = None
> +
> +    with open(fn) as f:
> +        try:
> +            relstr = f.readline().strip()
> +        except (IOError, AttributeError):
> +            relstr = ""
> +
> +    # get the release name and version
> +    # assumes that form is something
> +    # like "Red Hat Linux release 6.2 (Zoot)"
> +    (product, sep, version) = relstr.partition(" release ")
> +    if sep:
> +        relName = product
> +        relVer = version.split()[0]
> +
> +    return (relName, relVer)
> +
> +def releaseFromOsRelease(fn):
> +    """
> +    Attempt to identify the installation of a Linux distribution via
> +    /etc/os-release.  This file must already have been verified to exist
> +    and be readable.
> +
> +    :param fn: an open filehandle on /etc/os-release
> +    :type fn: filehandle
> +    :returns: The distribution's name and version, or None for either or both
> +    if they cannot be determined
> +    :rtype: (string, string)
> +    """
> +    relName = None
> +    relVer = None
> +
> +    with open(fn, "r") as f:
> +        parser = shlex.shlex(f)
> +
> +        while True:
> +            key = parser.get_token()
> +            if key == parser.eof:
> +                break
> +            elif key == "NAME":
> +                # Throw away the "=".
> +                parser.get_token()
> +                relName = parser.get_token().strip("'\"")
> +            elif key == "VERSION_ID":
> +                # Throw away the "=".
> +                parser.get_token()
> +                relVer = parser.get_token().strip("'\"")
> +
> +    return (relName, relVer)
> +
> +def getReleaseString():
> +    """
> +    Attempt to identify the installation of a Linux distribution by checking
> +    a previously mounted filesystem for several files.  The filesystem must
> +    be mounted under the target physical root.
> +
> +    :returns: The machine's arch, distribution name, and distribution version
> +    or None for any parts that cannot be determined
> +    :rtype: (string, string, string)
> +    """
> +    relName = None
> +    relVer = None
> +
> +    try:
> +        relArch = util.capture_output(["arch"], root=getSysroot()).strip()
> +    except OSError:
> +        relArch = None
> +
> +    filename = "%s/etc/redhat-release" % getSysroot()
> +    if os.access(filename, os.R_OK):
> +        (relName, relVer) = releaseFromRedhatRelease(filename)
> +    else:
> +        filename = "%s/etc/os-release" % getSysroot()
> +        if os.access(filename, os.R_OK):
> +            (relName, relVer) = releaseFromOsRelease(filename)
> +
> +    return (relArch, relName, relVer)
> +
> +def parseFSTab(devicetree, chroot=None):
> +    """ parse /etc/fstab and return a tuple of a mount dict and swap list """
> +    if not chroot or not os.path.isdir(chroot):
> +        chroot = getSysroot()
> +
> +    mounts = {}
> +    swaps = []
> +    path = "%s/etc/fstab" % chroot
> +    if not os.access(path, os.R_OK):
> +        # XXX should we raise an exception instead?
> +        log.info("cannot open %s for read", path)
> +        return (mounts, swaps)
> +
> +    blkidTab = BlkidTab(chroot=chroot)
> +    try:
> +        blkidTab.parse()
> +        log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> +    except Exception: # pylint: disable=broad-except
> +        log_exception_info(log.info, "error parsing blkid.tab")
> +        blkidTab = None
> +
> +    cryptTab = CryptTab(devicetree, blkidTab=blkidTab, chroot=chroot)
> +    try:
> +        cryptTab.parse(chroot=chroot)
> +        log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> +    except Exception: # pylint: disable=broad-except
> +        log_exception_info(log.info, "error parsing crypttab")
> +        cryptTab = None
> +
> +    with open(path) as f:
> +        log.debug("parsing %s", path)
> +        for line in f.readlines():
> +
> +            (line, _pound, _comment) = line.partition("#")
> +            fields = line.split(None, 4)
> +
> +            if len(fields) < 5:
> +                continue
> +
> +            (devspec, mountpoint, fstype, options, _rest) = fields
> +
> +            # find device in the tree
> +            device = devicetree.resolveDevice(devspec,
> +                                              cryptTab=cryptTab,
> +                                              blkidTab=blkidTab,
> +                                              options=options)
> +
> +            if device is None:
> +                continue
> +
> +            if fstype != "swap":
> +                mounts[mountpoint] = device
> +            else:
> +                swaps.append(device)
> +
> +    return (mounts, swaps)
> +
> +def findExistingInstallations(devicetree):
> +    if not os.path.exists(getTargetPhysicalRoot()):
> +        util.makedirs(getTargetPhysicalRoot())
> +
> +    roots = []
> +    for device in devicetree.leaves:
> +        if not device.format.linuxNative or not device.format.mountable or \
> +           not device.controllable:
> +            continue
> +
> +        try:
> +            device.setup()
> +        except Exception: # pylint: disable=broad-except
> +            log_exception_info(log.warning, "setup of %s failed", [device.name])
> +            continue
> +
> +        options = device.format.options + ",ro"
> +        try:
> +            device.format.mount(options=options, mountpoint=getSysroot())
> +        except Exception: # pylint: disable=broad-except
> +            log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
> +            device.teardown()
> +            continue
> +
> +        if not os.access(getSysroot() + "/etc/fstab", os.R_OK):
> +            device.teardown(recursive=True)
> +            continue
> +
> +        try:
> +            (architecture, product, version) = getReleaseString()
> +        except ValueError:
> +            name = _("Linux on %s") % device.name
> +        else:
> +            # I'd like to make this finer grained, but it'd be very difficult
> +            # to translate.
> +            if not product or not version or not architecture:
> +                name = _("Unknown Linux")
> +            elif "linux" in product.lower():
> +                name = _("%(product)s %(version)s for %(arch)s") % \
> +                        {"product": product, "version": version, "arch": architecture}
> +            else:
> +                name = _("%(product)s Linux %(version)s for %(arch)s") % \
> +                        {"product": product, "version": version, "arch": architecture}
> +
> +        (mounts, swaps) = parseFSTab(devicetree, chroot=getSysroot())
> +        device.teardown()
> +        if not mounts and not swaps:
> +            # empty /etc/fstab. weird, but I've seen it happen.
> +            continue
> +        roots.append(Root(mounts=mounts, swaps=swaps, name=name))
> +
> +    return roots
> +
> +class FSSet(object):
> +    """ A class to represent a set of filesystems. """
> +    def __init__(self, devicetree):
> +        self.devicetree = devicetree
> +        self.cryptTab = None
> +        self.blkidTab = None
> +        self.origFStab = None
> +        self.active = False
> +        self._dev = None
> +        self._devpts = None
> +        self._sysfs = None
> +        self._proc = None
> +        self._devshm = None
> +        self._usb = None
> +        self._selinux = None
> +        self._run = None
> +        self._fstab_swaps = set()
> +        self.preserveLines = []     # lines we just ignore and preserve
> +
> +    @property
> +    def sysfs(self):
> +        if not self._sysfs:
> +            self._sysfs = NoDevice(fmt=getFormat("sysfs", device="sysfs", mountpoint="/sys"))
> +        return self._sysfs
> +
> +    @property
> +    def dev(self):
> +        if not self._dev:
> +            self._dev = DirectoryDevice("/dev",
> +               fmt=getFormat("bind", device="/dev", mountpoint="/dev", exists=True),
> +               exists=True)
> +
> +        return self._dev
> +
> +    @property
> +    def devpts(self):
> +        if not self._devpts:
> +            self._devpts = NoDevice(fmt=getFormat("devpts", device="devpts", mountpoint="/dev/pts"))
> +        return self._devpts
> +
> +    @property
> +    def proc(self):
> +        if not self._proc:
> +            self._proc = NoDevice(fmt=getFormat("proc", device="proc", mountpoint="/proc"))
> +        return self._proc
> +
> +    @property
> +    def devshm(self):
> +        if not self._devshm:
> +            self._devshm = NoDevice(fmt=getFormat("tmpfs", device="tmpfs", mountpoint="/dev/shm"))
> +        return self._devshm
> +
> +    @property
> +    def usb(self):
> +        if not self._usb:
> +            self._usb = NoDevice(fmt=getFormat("usbfs", device="usbfs", mountpoint="/proc/bus/usb"))
> +        return self._usb
> +
> +    @property
> +    def selinux(self):
> +        if not self._selinux:
> +            self._selinux = NoDevice(fmt=getFormat("selinuxfs", device="selinuxfs", mountpoint="/sys/fs/selinux"))
> +        return self._selinux
> +
> +    @property
> +    def run(self):
> +        if not self._run:
> +            self._run = DirectoryDevice("/run",
> +               fmt=getFormat("bind", device="/run", mountpoint="/run", exists=True),
> +               exists=True)
> +
> +        return self._run
> +
> +    @property
> +    def devices(self):
> +        return sorted(self.devicetree.devices, key=lambda d: d.path)
> +
> +    @property
> +    def mountpoints(self):
> +        filesystems = {}
> +        for device in self.devices:
> +            if device.format.mountable and device.format.mountpoint:
> +                filesystems[device.format.mountpoint] = device
> +        return filesystems
> +
> +    def _parseOneLine(self, devspec, mountpoint, fstype, options, _dump="0", _passno="0"):
> +        """Parse an fstab entry for a device, return the corresponding device.
> +
> +           The parameters correspond to the items in a single entry in the
> +           order in which they occur in the entry.
> +
> +           :returns: the device corresponding to the entry
> +           :rtype: :class:`devices.Device`
> +        """
> +
> +        # no sense in doing any legwork for a noauto entry
> +        if "noauto" in options.split(","):
> +            log.info("ignoring noauto entry")
> +            raise UnrecognizedFSTabEntryError()
> +
> +        # find device in the tree
> +        device = self.devicetree.resolveDevice(devspec,
> +                                               cryptTab=self.cryptTab,
> +                                               blkidTab=self.blkidTab,
> +                                               options=options)
> +
> +        if device:
> +            # fall through to the bottom of this block
> +            pass
> +        elif devspec.startswith("/dev/loop"):
> +            # FIXME: create devices.LoopDevice
> +            log.warning("completely ignoring your loop mount")
> +        elif ":" in devspec and fstype.startswith("nfs"):
> +            # NFS -- preserve but otherwise ignore
> +            device = NFSDevice(devspec,
> +                               fmt=getFormat(fstype,
> +                                                exists=True,
> +                                                device=devspec))
> +        elif devspec.startswith("/") and fstype == "swap":
> +            # swap file
> +            device = FileDevice(devspec,
> +                                parents=get_containing_device(devspec, self.devicetree),
> +                                fmt=getFormat(fstype,
> +                                                 device=devspec,
> +                                                 exists=True),
> +                                exists=True)
> +        elif fstype == "bind" or "bind" in options:
> +            # bind mount... set fstype so later comparison won't
> +            # turn up false positives
> +            fstype = "bind"
> +
> +            # This is probably not going to do anything useful, so we'll
> +            # make sure to try again from FSSet.mountFilesystems. The bind
> +            # mount targets should be accessible by the time we try to do
> +            # the bind mount from there.
> +            parents = get_containing_device(devspec, self.devicetree)
> +            device = DirectoryDevice(devspec, parents=parents, exists=True)
> +            device.format = getFormat("bind",
> +                                      device=device.path,
> +                                      exists=True)
> +        elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts",
> +                            "/sys/fs/selinux", "/proc/bus/usb"):
> +            # drop these now -- we'll recreate later
> +            return None
> +        else:
> +            # nodev filesystem -- preserve or drop completely?
> +            fmt = getFormat(fstype)
> +            fmt_class = get_device_format_class("nodev")
> +            if devspec == "none" or \
> +               (fmt_class and isinstance(fmt, fmt_class)):
> +                device = NoDevice(fmt=fmt)
> +
> +        if device is None:
> +            log.error("failed to resolve %s (%s) from fstab", devspec,
> +                                                              fstype)
> +            raise UnrecognizedFSTabEntryError()
> +
> +        device.setup()
> +        fmt = getFormat(fstype, device=device.path, exists=True)
> +        if fstype != "auto" and None in (device.format.type, fmt.type):
> +            log.info("Unrecognized filesystem type for %s (%s)",
> +                     device.name, fstype)
> +            device.teardown()
> +            raise UnrecognizedFSTabEntryError()
> +
> +        # make sure, if we're using a device from the tree, that
> +        # the device's format we found matches what's in the fstab
> +        ftype = getattr(fmt, "mountType", fmt.type)
> +        dtype = getattr(device.format, "mountType", device.format.type)
> +        if fstype != "auto" and ftype != dtype:
> +            log.info("fstab says %s at %s is %s", dtype, mountpoint, ftype)
> +            if fmt.testMount():
> +                device.format = fmt
> +            else:
> +                device.teardown()
> +                raise FSTabTypeMismatchError("%s: detected as %s, fstab says %s"
> +                                             % (mountpoint, dtype, ftype))
> +        del ftype
> +        del dtype
> +
> +        if hasattr(device.format, "mountpoint"):
> +            device.format.mountpoint = mountpoint
> +
> +        device.format.options = options
> +
> +        return device
> +
> +    def parseFSTab(self, chroot=None):
> +        """ parse /etc/fstab
> +
> +            preconditions:
> +                all storage devices have been scanned, including filesystems
> +            postconditions:
> +
> +            FIXME: control which exceptions we raise
> +
> +            XXX do we care about bind mounts?
> +                how about nodev mounts?
> +                loop mounts?
> +        """
> +        if not chroot or not os.path.isdir(chroot):
> +            chroot = getSysroot()
> +
> +        path = "%s/etc/fstab" % chroot
> +        if not os.access(path, os.R_OK):
> +            # XXX should we raise an exception instead?
> +            log.info("cannot open %s for read", path)
> +            return
> +
> +        blkidTab = BlkidTab(chroot=chroot)
> +        try:
> +            blkidTab.parse()
> +            log.debug("blkid.tab devs: %s", list(blkidTab.devices.keys()))
> +        except Exception: # pylint: disable=broad-except
> +            log_exception_info(log.info, "error parsing blkid.tab")
> +            blkidTab = None
> +
> +        cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
> +        try:
> +            cryptTab.parse(chroot=chroot)
> +            log.debug("crypttab maps: %s", list(cryptTab.mappings.keys()))
> +        except Exception: # pylint: disable=broad-except
> +            log_exception_info(log.info, "error parsing crypttab")
> +            cryptTab = None
> +
> +        self.blkidTab = blkidTab
> +        self.cryptTab = cryptTab
> +
> +        with open(path) as f:
> +            log.debug("parsing %s", path)
> +
> +            lines = f.readlines()
> +
> +            # save the original file
> +            self.origFStab = ''.join(lines)
> +
> +            for line in lines:
> +
> +                (line, _pound, _comment) = line.partition("#")
> +                fields = line.split()
> +
> +                if not 4 <= len(fields) <= 6:
> +                    continue
> +
> +                try:
> +                    device = self._parseOneLine(*fields)
> +                except UnrecognizedFSTabEntryError:
> +                    # just write the line back out as-is after upgrade
> +                    self.preserveLines.append(line)
> +                    continue
> +
> +                if not device:
> +                    continue
> +
> +                if device not in self.devicetree.devices:
> +                    try:
> +                        self.devicetree._addDevice(device)
> +                    except ValueError:
> +                        # just write duplicates back out post-install
> +                        self.preserveLines.append(line)
> +
> +    def turnOnSwap(self, rootPath=""):
> +        """ Activate the system's swap space. """
> +        if not flags.installer_mode:
> +            return
> +
> +        for device in self.swapDevices:
> +            if isinstance(device, FileDevice):
> +                # set up FileDevices' parents now that they are accessible
> +                targetDir = "%s/%s" % (rootPath, device.path)
> +                parent = get_containing_device(targetDir, self.devicetree)
> +                if not parent:
> +                    log.error("cannot determine which device contains "
> +                              "directory %s", device.path)
> +                    device.parents = []
> +                    self.devicetree._removeDevice(device)
> +                    continue
> +                else:
> +                    device.parents = [parent]
> +
> +            while True:
> +                try:
> +                    device.setup()
> +                    device.format.setup()
> +                except StorageError as e:
> +                    if errorHandler.cb(e) == ERROR_RAISE:
> +                        raise
> +                else:
> +                    break
> +
> +    def mountFilesystems(self, rootPath="", readOnly=None, skipRoot=False):
> +        """ Mount the system's filesystems.
> +
> +            :param str rootPath: the root directory for this filesystem
> +            :param readOnly: read only option str for this filesystem
> +            :type readOnly: str or None
> +            :param bool skipRoot: whether to skip mounting the root filesystem
> +        """
> +        if not flags.installer_mode:
> +            return
> +
> +        devices = list(self.mountpoints.values()) + self.swapDevices
> +        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> +                        self.proc, self.selinux, self.usb, self.run])
> +        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> +
> +        for device in devices:
> +            if not device.format.mountable or not device.format.mountpoint:
> +                continue
> +
> +            if skipRoot and device.format.mountpoint == "/":
> +                continue
> +
> +            options = device.format.options
> +            if "noauto" in options.split(","):
> +                continue
> +
> +            if device.format.type == "bind" and device not in [self.dev, self.run]:
> +                # set up the DirectoryDevice's parents now that they are
> +                # accessible
> +                #
> +                # -- bind formats' device and mountpoint are always both
> +                #    under the chroot. no exceptions. none, damn it.
> +                targetDir = "%s/%s" % (rootPath, device.path)
> +                parent = get_containing_device(targetDir, self.devicetree)
> +                if not parent:
> +                    log.error("cannot determine which device contains "
> +                              "directory %s", device.path)
> +                    device.parents = []
> +                    self.devicetree._removeDevice(device)
> +                    continue
> +                else:
> +                    device.parents = [parent]
> +
> +            try:
> +                device.setup()
> +            except Exception as e: # pylint: disable=broad-except
> +                log_exception_info(fmt_str="unable to set up device %s", fmt_args=[device])
> +                if errorHandler.cb(e) == ERROR_RAISE:
> +                    raise
> +                else:
> +                    continue
> +
> +            if readOnly:
> +                options = "%s,%s" % (options, readOnly)
> +
> +            try:
> +                device.format.setup(options=options,
> +                                    chroot=rootPath)
> +            except Exception as e: # pylint: disable=broad-except
> +                log_exception_info(log.error, "error mounting %s on %s", [device.path, device.format.mountpoint])
> +                if errorHandler.cb(e) == ERROR_RAISE:
> +                    raise
> +
> +        self.active = True
> +
> +    def umountFilesystems(self, swapoff=True):
> +        """ unmount filesystems, except swap if swapoff == False """
> +        devices = list(self.mountpoints.values()) + self.swapDevices
> +        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs,
> +                        self.proc, self.usb, self.selinux, self.run])
> +        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
> +        devices.reverse()
> +        for device in devices:
> +            if (not device.format.mountable) or \
> +               (device.format.type == "swap" and not swapoff):
> +                continue
> +
> +            device.format.teardown()
> +            device.teardown()
> +
> +        self.active = False
> +
> +    def createSwapFile(self, device, size):
> +        """ Create and activate a swap file under storage root. """
> +        filename = "/SWAP"
> +        count = 0
> +        basedir = os.path.normpath("%s/%s" % (getTargetPhysicalRoot(),
> +                                              device.format.mountpoint))
> +        while os.path.exists("%s/%s" % (basedir, filename)) or \
> +              self.devicetree.getDeviceByName(filename):
> +            count += 1
> +            filename = "/SWAP-%d" % count
> +
> +        dev = FileDevice(filename,
> +                         size=size,
> +                         parents=[device],
> +                         fmt=getFormat("swap", device=filename))
> +        dev.create()
> +        dev.setup()
> +        dev.format.create()
> +        dev.format.setup()
> +        # nasty, nasty
> +        self.devicetree._addDevice(dev)
> +
> +    def mkDevRoot(self):
> +        root = self.rootDevice
> +        dev = "%s/%s" % (getSysroot(), root.path)
> +        if not os.path.exists("%s/dev/root" %(getSysroot(),)) and os.path.exists(dev):
> +            rdev = os.stat(dev).st_rdev
> +            os.mknod("%s/dev/root" % (getSysroot(),), stat.S_IFBLK | 0o600, rdev)
> +
> +    @property
> +    def swapDevices(self):
> +        swaps = []
> +        for device in self.devices:
> +            if device.format.type == "swap":
> +                swaps.append(device)
> +        return swaps
> +
> +    @property
> +    def rootDevice(self):
> +        for path in ["/", getTargetPhysicalRoot()]:
> +            for device in self.devices:
> +                try:
> +                    mountpoint = device.format.mountpoint
> +                except AttributeError:
> +                    mountpoint = None
> +
> +                if mountpoint == path:
> +                    return device
> +
> +    def write(self):
> +        """ write out all config files based on the set of filesystems """
> +        # /etc/fstab
> +        fstab_path = os.path.normpath("%s/etc/fstab" % getSysroot())
> +        fstab = self.fstab()
> +        open(fstab_path, "w").write(fstab)
> +
> +        # /etc/crypttab
> +        crypttab_path = os.path.normpath("%s/etc/crypttab" % getSysroot())
> +        crypttab = self.crypttab()
> +        origmask = os.umask(0o077)
> +        open(crypttab_path, "w").write(crypttab)
> +        os.umask(origmask)
> +
> +        # /etc/mdadm.conf
> +        mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % getSysroot())
> +        mdadm_conf = self.mdadmConf()
> +        if mdadm_conf:
> +            open(mdadm_path, "w").write(mdadm_conf)
> +
> +        # /etc/multipath.conf
> +        if self.devicetree.getDevicesByType("dm-multipath"):
> +            util.copy_to_system("/etc/multipath.conf")
> +            util.copy_to_system("/etc/multipath/wwids")
> +            util.copy_to_system("/etc/multipath/bindings")
> +        else:
> +            log.info("not writing out mpath configuration")
> +
> +    def crypttab(self):
> +        # if we are upgrading, do we want to update crypttab?
> +        # gut reaction says no, but plymouth needs the names to be very
> +        # specific for passphrase prompting
> +        if not self.cryptTab:
> +            self.cryptTab = CryptTab(self.devicetree)
> +            self.cryptTab.populate()
> +
> +        devices = list(self.mountpoints.values()) + self.swapDevices
> +
> +        # prune crypttab -- only mappings required by one or more entries
> +        for name in self.cryptTab.mappings.keys():
> +            keep = False
> +            mapInfo = self.cryptTab[name]
> +            cryptoDev = mapInfo['device']
> +            for device in devices:
> +                if device == cryptoDev or device.dependsOn(cryptoDev):
> +                    keep = True
> +                    break
> +
> +            if not keep:
> +                del self.cryptTab.mappings[name]
> +
> +        return self.cryptTab.crypttab()
> +
> +    def mdadmConf(self):
> +        """ Return the contents of mdadm.conf. """
> +        arrays = self.devicetree.getDevicesByType("mdarray")
> +        arrays.extend(self.devicetree.getDevicesByType("mdbiosraidarray"))
> +        arrays.extend(self.devicetree.getDevicesByType("mdcontainer"))
> +        # Sort it, this not only looks nicer, but this will also put
> +        # containers (which get md0, md1, etc.) before their members
> +        # (which get md127, md126, etc.). and lame as it is mdadm will not
> +        # assemble the whole stack in one go unless listed in the proper order
> +        # in mdadm.conf
> +        arrays.sort(key=lambda d: d.path)
> +        if not arrays:
> +            return ""
> +
> +        conf = "# mdadm.conf written out by anaconda\n"
> +        conf += "MAILADDR root\n"
> +        conf += "AUTO +imsm +1.x -all\n"
> +        devices = list(self.mountpoints.values()) + self.swapDevices
> +        for array in arrays:
> +            for device in devices:
> +                if device == array or device.dependsOn(array):
> +                    conf += array.mdadmConfEntry
> +                    break
> +
> +        return conf
> +
> +    def fstab (self):
> +        fmt_str = "%-23s %-23s %-7s %-15s %d %d\n"
> +        fstab = """
> +#
> +# /etc/fstab
> +# Created by anaconda on %s
> +#
> +# Accessible filesystems, by reference, are maintained under '/dev/disk'
> +# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
> +#
> +""" % time.asctime()
> +
> +        devices = sorted(self.mountpoints.values(),
> +                         key=lambda d: d.format.mountpoint)
> +
> +        # filter swaps only in installer mode
> +        if flags.installer_mode:
> +            devices += [dev for dev in self.swapDevices
> +                        if dev in self._fstab_swaps]
> +        else:
> +            devices += self.swapDevices
> +
> +        netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
> +
> +        rootdev = devices[0]
> +        root_on_netdev = any(rootdev.dependsOn(netdev) for netdev in netdevs)
> +
> +        for device in devices:
> +            # why the hell do we put swap in the fstab, anyway?
> +            if not device.format.mountable and device.format.type != "swap":
> +                continue
> +
> +            # Don't write out lines for optical devices, either.
> +            if isinstance(device, OpticalDevice):
> +                continue
> +
> +            fstype = getattr(device.format, "mountType", device.format.type)
> +            if fstype == "swap":
> +                mountpoint = "swap"
> +                options = device.format.options
> +            else:
> +                mountpoint = device.format.mountpoint
> +                options = device.format.options
> +                if not mountpoint:
> +                    log.warning("%s filesystem on %s has no mountpoint",
> +                                                            fstype,
> +                                                            device.path)
> +                    continue
> +
> +            options = options or "defaults"
> +            for netdev in netdevs:
> +                if device.dependsOn(netdev):
> +                    if root_on_netdev and mountpoint not in ["/", "/usr"]:
> +                        options = options + ",x-initrd.mount"
> +                    break
> +            if device.encrypted:
> +                options += ",x-systemd.device-timeout=0"
> +            devspec = device.fstabSpec
> +            dump = device.format.dump
> +            if device.format.check and mountpoint == "/":
> +                passno = 1
> +            elif device.format.check:
> +                passno = 2
> +            else:
> +                passno = 0
> +            fstab = fstab + device.fstabComment
> +            fstab = fstab + fmt_str % (devspec, mountpoint, fstype,
> +                                       options, dump, passno)
> +
> +        # now, write out any lines we were unable to process because of
> +        # unrecognized filesystems or unresolveable device specifications
> +        for line in self.preserveLines:
> +            fstab += line
> +
> +        return fstab
> +
> +    def addFstabSwap(self, device):
> +        """
> +        Add swap device to the list of swaps that should appear in the fstab.
> +
> +        :param device: swap device that should be added to the list
> +        :type device: blivet.devices.StorageDevice instance holding a swap format
> +
> +        """
> +
> +        self._fstab_swaps.add(device)
> +
> +    def removeFstabSwap(self, device):
> +        """
> +        Remove swap device from the list of swaps that should appear in the fstab.
> +
> +        :param device: swap device that should be removed from the list
> +        :type device: blivet.devices.StorageDevice instance holding a swap format
> +
> +        """
> +
> +        try:
> +            self._fstab_swaps.remove(device)
> +        except KeyError:
> +            pass
> +
> +    def setFstabSwaps(self, devices):
> +        """
> +        Set swap devices that should appear in the fstab.
> +
> +        :param devices: iterable providing devices that should appear in the fstab
> +        :type devices: iterable providing blivet.devices.StorageDevice instances holding
> +                       a swap format
> +
> +        """
> +
> +        self._fstab_swaps = set(devices)
> +
> +class Root(object):
> +    """ A Root represents an existing OS installation. """
> +    def __init__(self, mounts=None, swaps=None, name=None):
> +        """
> +            :keyword mounts: mountpoint dict
> +            :type mounts: dict (mountpoint keys and :class:`~.devices.StorageDevice` values)
> +            :keyword swaps: swap device list
> +            :type swaps: list of :class:`~.devices.StorageDevice`
> +            :keyword name: name for this installed OS
> +            :type name: str
> +        """
> +        # mountpoint key, StorageDevice value
> +        if not mounts:
> +            self.mounts = {}
> +        else:
> +            self.mounts = mounts
> +
> +        # StorageDevice
> +        if not swaps:
> +            self.swaps = []
> +        else:
> +            self.swaps = swaps
> +
> +        self.name = name    # eg: "Fedora Linux 16 for x86_64", "Linux on sda2"
> +
> +        if not self.name and "/" in self.mounts:
> +            self.name = self.mounts["/"].format.uuid
> +
> +    @property
> +    def device(self):
> +        return self.mounts.get("/")
> +
> +
> +class BlkidTab(object):
> +    """ Dictionary-like interface to blkid.tab with device path keys """
> +    def __init__(self, chroot=""):
> +        self.chroot = chroot
> +        self.devices = {}
> +
> +    def parse(self):
> +        path = "%s/etc/blkid/blkid.tab" % self.chroot
> +        log.debug("parsing %s", path)
> +        with open(path) as f:
> +            for line in f.readlines():
> +                # this is pretty ugly, but an XML parser is more work than
> +                # is justifiable for this purpose
> +                if not line.startswith("<device "):
> +                    continue
> +
> +                line = line[len("<device "):-len("</device>\n")]
> +
> +                (data, _sep, device) = line.partition(">")
> +                if not device:
> +                    continue
> +
> +                self.devices[device] = {}
> +                for pair in data.split():
> +                    try:
> +                        (key, value) = pair.split("=")
> +                    except ValueError:
> +                        continue
> +
> +                    self.devices[device][key] = value[1:-1] # strip off quotes
> +
> +    def __getitem__(self, key):
> +        return self.devices[key]
> +
> +    def get(self, key, default=None):
> +        return self.devices.get(key, default)
> +
> +
> +class CryptTab(object):
> +    """ Dictionary-like interface to crypttab entries with map name keys """
> +    def __init__(self, devicetree, blkidTab=None, chroot=""):
> +        self.devicetree = devicetree
> +        self.blkidTab = blkidTab
> +        self.chroot = chroot
> +        self.mappings = {}
> +
> +    def parse(self, chroot=""):
> +        """ Parse /etc/crypttab from an existing installation. """
> +        if not chroot or not os.path.isdir(chroot):
> +            chroot = ""
> +
> +        path = "%s/etc/crypttab" % chroot
> +        log.debug("parsing %s", path)
> +        with open(path) as f:
> +            if not self.blkidTab:
> +                try:
> +                    self.blkidTab = BlkidTab(chroot=chroot)
> +                    self.blkidTab.parse()
> +                except Exception: # pylint: disable=broad-except
> +                    log_exception_info(fmt_str="failed to parse blkid.tab")
> +                    self.blkidTab = None
> +
> +            for line in f.readlines():
> +                (line, _pound, _comment) = line.partition("#")
> +                fields = line.split()
> +                if not 2 <= len(fields) <= 4:
> +                    continue
> +                elif len(fields) == 2:
> +                    fields.extend(['none', ''])
> +                elif len(fields) == 3:
> +                    fields.append('')
> +
> +                (name, devspec, keyfile, options) = fields
> +
> +                # resolve devspec to a device in the tree
> +                device = self.devicetree.resolveDevice(devspec,
> +                                                       blkidTab=self.blkidTab)
> +                if device:
> +                    self.mappings[name] = {"device": device,
> +                                           "keyfile": keyfile,
> +                                           "options": options}
> +
> +    def populate(self):
> +        """ Populate the instance based on the device tree's contents. """
> +        for device in self.devicetree.devices:
> +            # XXX should we put them all in there or just the ones that
> +            #     are part of a device containing swap or a filesystem?
> +            #
> +            #       Put them all in here -- we can filter from FSSet
> +            if device.format.type != "luks":
> +                continue
> +
> +            key_file = device.format.keyFile
> +            if not key_file:
> +                key_file = "none"
> +
> +            options = device.format.options or ""
> +
> +            self.mappings[device.format.mapName] = {"device": device,
> +                                                    "keyfile": key_file,
> +                                                    "options": options}
> +
> +    def crypttab(self):
> +        """ Write out /etc/crypttab """
> +        crypttab = ""
> +        for name in self.mappings:
> +            entry = self[name]
> +            crypttab += "%s UUID=%s %s %s\n" % (name,
> +                                                entry['device'].format.uuid,
> +                                                entry['keyfile'],
> +                                                entry['options'])
> +        return crypttab
> +
> +    def __getitem__(self, key):
> +        return self.mappings[key]
> +
> +    def get(self, key, default=None):
> +        return self.mappings.get(key, default)
> +
> +def get_containing_device(path, devicetree):
> +    """ Return the device that a path resides on. """
> +    if not os.path.exists(path):
> +        return None
> +
> +    st = os.stat(path)
> +    major = os.major(st.st_dev)
> +    minor = os.minor(st.st_dev)
> +    link = "/sys/dev/block/%s:%s" % (major, minor)
> +    if not os.path.exists(link):
> +        return None
> +
> +    try:
> +        device_name = os.path.basename(os.readlink(link))
> +    except Exception: # pylint: disable=broad-except
> +        log_exception_info(fmt_str="failed to find device name for path %s", fmt_args=[path])
> +        return None
> +
> +    if device_name.startswith("dm-"):
> +        # have I told you lately that I love you, device-mapper?
> +        device_name = name_from_dm_node(device_name)
> +
> +    return devicetree.getDeviceByName(device_name)
> +
> +def turnOnFilesystems(storage, mountOnly=False, callbacks=None):
> +    """
> +    Perform installer-specific activation of storage configuration.
> +
> +    :param callbacks: callbacks to be invoked when actions are executed
> +    :type callbacks: return value of the :func:`~.callbacks.create_new_callbacks_register`
> +
> +    """
> +
> +    if not flags.installer_mode:
> +        return
> +
> +    if not mountOnly:
> +        if (flags.live_install and not flags.image_install and not storage.fsset.active):
> +            # turn off any swaps that we didn't turn on
> +            # needed for live installs
> +            util.run_program(["swapoff", "-a"])
> +        storage.devicetree.teardownAll()
> +
> +        try:
> +            storage.doIt(callbacks)
> +        except FSResizeError as e:
> +            if errorHandler.cb(e) == ERROR_RAISE:
> +                raise
> +        except Exception as e:
> +            raise
> +
> +        storage.turnOnSwap()
> +    # FIXME:  For livecd, skipRoot needs to be True.
> +    storage.mountFilesystems()
> +
> +    if not mountOnly:
> +        writeEscrowPackets(storage)
> +
> +def writeEscrowPackets(storage):
> +    escrowDevices = [d for d in storage.devices if d.format.type == 'luks' and
> +                     d.format.escrow_cert]
> +
> +    if not escrowDevices:
> +        return
> +
> +    log.debug("escrow: writeEscrowPackets start")
> +
> +    if not nss:
> +        log.error("escrow: no nss python module -- aborting")
> +        return
> +
> +    nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized
> +
> +    backupPassphrase = generateBackupPassphrase()
> +
> +    try:
> +        escrowDir = getSysroot() + "/root"
> +        log.debug("escrow: writing escrow packets to %s", escrowDir)
> +        util.makedirs(escrowDir)
> +        for device in escrowDevices:
> +            log.debug("escrow: device %s: %s",
> +                      repr(device.path), repr(device.format.type))
> +            device.format.escrow(escrowDir,
> +                                 backupPassphrase)
> +
> +    except (IOError, RuntimeError) as e:
> +        # TODO: real error handling
> +        log.error("failed to store encryption key: %s", e)
> +
> +    log.debug("escrow: writeEscrowPackets done")
> diff --git a/blivet/partitioning.py b/blivet/partitioning.py
> index f59f015..17da6e0 100644
> --- a/blivet/partitioning.py
> +++ b/blivet/partitioning.py
> @@ -24,13 +24,10 @@ from operator import gt, lt
>   from decimal import Decimal
>
>   import parted
> -from pykickstart.constants import AUTOPART_TYPE_BTRFS, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP, AUTOPART_TYPE_PLAIN
>
> -from .errors import DeviceError, NoDisksError, NotEnoughFreeSpaceError, PartitioningError
> +from .errors import DeviceError, PartitioningError
>   from .flags import flags
>   from .devices import PartitionDevice, LUKSDevice, devicePathToName
> -from .devices.partition import FALLBACK_DEFAULT_PART_SIZE
> -from .formats import getFormat
>   from .devicelibs.lvm import get_pool_padding
>   from .size import Size
>   from .i18n import _
> @@ -39,387 +36,6 @@ from .util import stringize, unicodeize
>   import logging
>   log = logging.getLogger("blivet")
>
> -def _getCandidateDisks(storage):
> -    """ Return a list of disks to be used for autopart.
> -
> -        Disks must be partitioned and have a single free region large enough
> -        for a default-sized (500MiB) partition. They must also be in
> -        :attr:`StorageDiscoveryConfig.clearPartDisks` if it is non-empty.
> -
> -        :param storage: a Blivet instance
> -        :type storage: :class:`~.Blivet`
> -        :returns: a list of partitioned disks with at least 500MiB of free space
> -        :rtype: list of :class:`~.devices.StorageDevice`
> -    """
> -    disks = []
> -    for disk in storage.partitioned:
> -        if storage.config.clearPartDisks and \
> -           (disk.name not in storage.config.clearPartDisks):
> -            continue
> -
> -        part = disk.format.firstPartition
> -        while part:
> -            if not part.type & parted.PARTITION_FREESPACE:
> -                part = part.nextPartition()
> -                continue
> -
> -            if Size(part.getLength(unit="B")) > PartitionDevice.defaultSize:
> -                disks.append(disk)
> -                break
> -
> -            part = part.nextPartition()
> -
> -    return disks
> -
> -def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> -    """ Schedule creation of a lvm/btrfs member partitions for autopart.
> -
> -        We create one such partition on each disk. They are not allocated until
> -        later (in :func:`doPartitioning`).
> -
> -        :param storage: a :class:`~.Blivet` instance
> -        :type storage: :class:`~.Blivet`
> -        :param disks: list of partitioned disks with free space
> -        :type disks: list of :class:`~.devices.StorageDevice`
> -        :param min_luks_entropy: minimum entropy in bits required for
> -                                 luks format creation
> -        :type min_luks_entropy: int
> -        :returns: list of newly created (unallocated) partitions
> -        :rtype: list of :class:`~.devices.PartitionDevice`
> -    """
> -    # create a separate pv or btrfs partition for each disk with free space
> -    devs = []
> -
> -    # only schedule the partitions if either lvm or btrfs autopart was chosen
> -    if storage.autoPartType == AUTOPART_TYPE_PLAIN:
> -        return devs
> -
> -    for disk in disks:
> -        if storage.encryptedAutoPart:
> -            fmt_type = "luks"
> -            fmt_args = {"passphrase": storage.encryptionPassphrase,
> -                        "cipher": storage.encryptionCipher,
> -                        "escrow_cert": storage.autoPartEscrowCert,
> -                        "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> -                        "min_luks_entropy": min_luks_entropy}
> -        else:
> -            if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> -                fmt_type = "lvmpv"
> -            else:
> -                fmt_type = "btrfs"
> -            fmt_args = {}
> -        part = storage.newPartition(fmt_type=fmt_type,
> -                                                fmt_args=fmt_args,
> -                                                grow=True,
> -                                                parents=[disk])
> -        storage.createDevice(part)
> -        devs.append(part)
> -
> -    return devs
> -
> -def _schedulePartitions(storage, disks, implicit_devices, min_luks_entropy=0):
> -    """ Schedule creation of autopart partitions.
> -
> -        This only schedules the requests for actual partitions.
> -
> -        :param storage: a :class:`~.Blivet` instance
> -        :type storage: :class:`~.Blivet`
> -        :param disks: list of partitioned disks with free space
> -        :type disks: list of :class:`~.devices.StorageDevice`
> -        :param min_luks_entropy: minimum entropy in bits required for
> -                                 luks format creation
> -        :type min_luks_entropy: int
> -        :returns: None
> -        :rtype: None
> -    """
> -    # basis for requests with requiredSpace is the sum of the sizes of the
> -    # two largest free regions
> -    all_free = (Size(reg.getLength(unit="B")) for reg in getFreeRegions(disks))
> -    all_free = sorted(all_free, reverse=True)
> -    if not all_free:
> -        # this should never happen since we've already filtered the disks
> -        # to those with at least 500MiB free
> -        log.error("no free space on disks %s", [d.name for d in disks])
> -        return
> -
> -    free = all_free[0]
> -    if len(all_free) > 1:
> -        free += all_free[1]
> -
> -    # The boot disk must be set at this point. See if any platform-specific
> -    # stage1 device we might allocate already exists on the boot disk.
> -    stage1_device = None
> -    for device in storage.devices:
> -        if storage.bootloader.stage1_disk not in device.disks:
> -            continue
> -
> -        if storage.bootloader.is_valid_stage1_device(device, early=True):
> -            stage1_device = device
> -            break
> -
> -    #
> -    # First pass is for partitions only. We'll do LVs later.
> -    #
> -    for request in storage.autoPartitionRequests:
> -        if ((request.lv and
> -             storage.autoPartType in (AUTOPART_TYPE_LVM,
> -                                      AUTOPART_TYPE_LVM_THINP)) or
> -            (request.btr and storage.autoPartType == AUTOPART_TYPE_BTRFS)):
> -            continue
> -
> -        if request.requiredSpace and request.requiredSpace > free:
> -            continue
> -
> -        elif request.fstype in ("prepboot", "efi", "macefi", "hfs+") and \
> -             (storage.bootloader.skip_bootloader or stage1_device):
> -            # there should never be a need for more than one of these
> -            # partitions, so skip them.
> -            log.info("skipping unneeded stage1 %s request", request.fstype)
> -            log.debug("%s", request)
> -
> -            if request.fstype in ["efi", "macefi"] and stage1_device:
> -                # Set the mountpoint for the existing EFI boot partition
> -                stage1_device.format.mountpoint = "/boot/efi"
> -
> -            log.debug("%s", stage1_device)
> -            continue
> -        elif request.fstype == "biosboot":
> -            is_gpt = (stage1_device and
> -                      getattr(stage1_device.format, "labelType", None) == "gpt")
> -            has_bios_boot = (stage1_device and
> -                             any([p.format.type == "biosboot"
> -                                    for p in storage.partitions
> -                                        if p.disk == stage1_device]))
> -            if (storage.bootloader.skip_bootloader or
> -                not (stage1_device and stage1_device.isDisk and
> -                    is_gpt and not has_bios_boot)):
> -                # there should never be a need for more than one of these
> -                # partitions, so skip them.
> -                log.info("skipping unneeded stage1 %s request", request.fstype)
> -                log.debug("%s", request)
> -                log.debug("%s", stage1_device)
> -                continue
> -
> -        if request.size > all_free[0]:
> -            # no big enough free space for the requested partition
> -            raise NotEnoughFreeSpaceError(_("No big enough free space on disks for "
> -                                            "automatic partitioning"))
> -
> -        if request.encrypted and storage.encryptedAutoPart:
> -            fmt_type = "luks"
> -            fmt_args = {"passphrase": storage.encryptionPassphrase,
> -                        "cipher": storage.encryptionCipher,
> -                        "escrow_cert": storage.autoPartEscrowCert,
> -                        "add_backup_passphrase": storage.autoPartAddBackupPassphrase,
> -                        "min_luks_entropy": min_luks_entropy}
> -        else:
> -            fmt_type = request.fstype
> -            fmt_args = {}
> -
> -        dev = storage.newPartition(fmt_type=fmt_type,
> -                                            fmt_args=fmt_args,
> -                                            size=request.size,
> -                                            grow=request.grow,
> -                                            maxsize=request.maxSize,
> -                                            mountpoint=request.mountpoint,
> -                                            parents=disks,
> -                                            weight=request.weight)
> -
> -        # schedule the device for creation
> -        storage.createDevice(dev)
> -
> -        if request.encrypted and storage.encryptedAutoPart:
> -            luks_fmt = getFormat(request.fstype,
> -                                 device=dev.path,
> -                                 mountpoint=request.mountpoint)
> -            luks_dev = LUKSDevice("luks-%s" % dev.name,
> -                                  fmt=luks_fmt,
> -                                  size=dev.size,
> -                                  parents=dev)
> -            storage.createDevice(luks_dev)
> -
> -        if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP,
> -                                    AUTOPART_TYPE_BTRFS):
> -            # doing LVM/BTRFS -- make sure the newly created partition fits in some
> -            # free space together with one of the implicitly requested partitions
> -            smallest_implicit = sorted(implicit_devices, key=lambda d: d.size)[0]
> -            if (request.size + smallest_implicit.size) > all_free[0]:
> -                # not enough space to allocate the smallest implicit partition
> -                # and the request, make the implicit partitions smaller in
> -                # attempt to make space for the request
> -                for implicit_req in implicit_devices:
> -                    implicit_req.size = FALLBACK_DEFAULT_PART_SIZE
> -
> -    return implicit_devices
> -
> -def _scheduleVolumes(storage, devs):
> -    """ Schedule creation of autopart lvm/btrfs volumes.
> -
> -        Schedules encryption of member devices if requested, schedules creation
> -        of the container (:class:`~.devices.LVMVolumeGroupDevice` or
> -        :class:`~.devices.BTRFSVolumeDevice`) then schedules creation of the
> -        autopart volume requests.
> -
> -        :param storage: a :class:`~.Blivet` instance
> -        :type storage: :class:`~.Blivet`
> -        :param devs: list of member partitions
> -        :type devs: list of :class:`~.devices.PartitionDevice`
> -        :returns: None
> -        :rtype: None
> -
> -        If an appropriate bootloader stage1 device exists on the boot drive, any
> -        autopart request to create another one will be skipped/discarded.
> -    """
> -    if not devs:
> -        return
> -
> -    if storage.autoPartType in (AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP):
> -        new_container = storage.newVG
> -        new_volume = storage.newLV
> -        format_name = "lvmpv"
> -    else:
> -        new_container = storage.newBTRFS
> -        new_volume = storage.newBTRFS
> -        format_name = "btrfs"
> -
> -    if storage.encryptedAutoPart:
> -        pvs = []
> -        for dev in devs:
> -            pv = LUKSDevice("luks-%s" % dev.name,
> -                            fmt=getFormat(format_name, device=dev.path),
> -                            size=dev.size,
> -                            parents=dev)
> -            pvs.append(pv)
> -            storage.createDevice(pv)
> -    else:
> -        pvs = devs
> -
> -    # create a vg containing all of the autopart pvs
> -    container = new_container(parents=pvs)
> -    storage.createDevice(container)
> -
> -    #
> -    # Convert storage.autoPartitionRequests into Device instances and
> -    # schedule them for creation.
> -    #
> -    # Second pass, for LVs only.
> -    pool = None
> -    for request in storage.autoPartitionRequests:
> -        btr = storage.autoPartType == AUTOPART_TYPE_BTRFS and request.btr
> -        lv = (storage.autoPartType in (AUTOPART_TYPE_LVM,
> -                                       AUTOPART_TYPE_LVM_THINP) and request.lv)
> -        thinlv = (storage.autoPartType == AUTOPART_TYPE_LVM_THINP and
> -                  request.lv and request.thin)
> -        if thinlv and pool is None:
> -            # create a single thin pool in the vg
> -            pool = storage.newLV(parents=[container], thin_pool=True, grow=True)
> -            storage.createDevice(pool)
> -
> -        if not btr and not lv and not thinlv:
> -            continue
> -
> -        # required space isn't relevant on btrfs
> -        if (lv or thinlv) and \
> -           request.requiredSpace and request.requiredSpace > container.size:
> -            continue
> -
> -        if request.fstype is None:
> -            if btr:
> -                # btrfs volumes can only contain btrfs filesystems
> -                request.fstype = "btrfs"
> -            else:
> -                request.fstype = storage.defaultFSType
> -
> -        kwargs = {"mountpoint": request.mountpoint,
> -                  "fmt_type": request.fstype}
> -        if lv or thinlv:
> -            if thinlv:
> -                parents = [pool]
> -            else:
> -                parents = [container]
> -
> -            kwargs.update({"parents": parents,
> -                           "grow": request.grow,
> -                           "maxsize": request.maxSize,
> -                           "size": request.size,
> -                           "thin_volume": thinlv})
> -        else:
> -            kwargs.update({"parents": [container],
> -                           "size": request.size,
> -                           "subvol": True})
> -
> -        dev = new_volume(**kwargs)
> -
> -        # schedule the device for creation
> -        storage.createDevice(dev)
> -
> -def doAutoPartition(storage, data, min_luks_entropy=0):
> -    """ Perform automatic partitioning.
> -
> -        :param storage: a :class:`~.Blivet` instance
> -        :type storage: :class:`~.Blivet`
> -        :param data: kickstart data
> -        :type data: :class:`pykickstart.BaseHandler`
> -        :param min_luks_entropy: minimum entropy in bits required for
> -                                 luks format creation
> -        :type min_luks_entropy: int
> -
> -        :attr:`Blivet.doAutoPart` controls whether this method creates the
> -        automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> -        which variant of autopart used. It uses one of the pykickstart
> -        AUTOPART_TYPE_* constants. The set of eligible disks is defined in
> -        :attr:`StorageDiscoveryConfig.clearPartDisks`.
> -
> -        .. note::
> -
> -            Clearing of partitions is handled separately, in
> -            :meth:`~.Blivet.clearPartitions`.
> -    """
> -    # pylint: disable=unused-argument
> -    log.debug("doAutoPart: %s", storage.doAutoPart)
> -    log.debug("encryptedAutoPart: %s", storage.encryptedAutoPart)
> -    log.debug("autoPartType: %s", storage.autoPartType)
> -    log.debug("clearPartType: %s", storage.config.clearPartType)
> -    log.debug("clearPartDisks: %s", storage.config.clearPartDisks)
> -    log.debug("autoPartitionRequests:\n%s", "".join([str(p) for p in storage.autoPartitionRequests]))
> -    log.debug("storage.disks: %s", [d.name for d in storage.disks])
> -    log.debug("storage.partitioned: %s", [d.name for d in storage.partitioned])
> -    log.debug("all names: %s", [d.name for d in storage.devices])
> -    log.debug("boot disk: %s", getattr(storage.bootDisk, "name", None))
> -
> -    disks = []
> -    devs = []
> -
> -    if not storage.doAutoPart:
> -        return
> -
> -    if not storage.partitioned:
> -        raise NoDisksError(_("No usable disks selected"))
> -
> -    disks = _getCandidateDisks(storage)
> -    devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> -    log.debug("candidate disks: %s", disks)
> -    log.debug("devs: %s", devs)
> -
> -    if disks == []:
> -        raise NotEnoughFreeSpaceError(_("Not enough free space on disks for "
> -                                      "automatic partitioning"))
> -
> -    devs = _schedulePartitions(storage, disks, devs, min_luks_entropy=min_luks_entropy)
> -
> -    # run the autopart function to allocate and grow partitions
> -    doPartitioning(storage)
> -    _scheduleVolumes(storage, devs)
> -
> -    # grow LVs
> -    growLVM(storage)
> -
> -    storage.setUpBootLoader()
> -
> -    # only newly added swaps should appear in the fstab
> -    new_swaps = (dev for dev in storage.swaps if not dev.format.exists)
> -    storage.setFstabSwaps(new_swaps)
> -
>   def partitionCompare(part1, part2):
>       """ More specifically defined partitions come first.
>
>



More information about the anaconda-patches mailing list