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

Vratislav Podzimek vpodzime at redhat.com
Wed Feb 11 15:46:26 UTC 2015


This code belongs to separate modules.

Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
---
 blivet/__init__.py        | 1010 +------------------------------------------
 blivet/autopart.py        |  483 +++++++++++++++++++++
 blivet/devicelibs/swap.py |   67 ---
 blivet/osinstall.py       | 1035 +++++++++++++++++++++++++++++++++++++++++++++
 blivet/partitioning.py    |  386 +----------------
 5 files changed, 1530 insertions(+), 1451 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..c566f6f 100644
--- a/blivet/__init__.py
+++ b/blivet/__init__.py
@@ -50,10 +50,8 @@ get_bootloader = lambda: None
 
 import os
 import time
-import stat
 import copy
 import tempfile
-import shlex
 import re
 
 try:
@@ -66,15 +64,13 @@ 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 FSResizeError, 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
@@ -302,6 +298,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 +442,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 +2077,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..fd037f5
--- /dev/null
+++ b/blivet/autopart.py
@@ -0,0 +1,483 @@
+#
+# Copyright (C) 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): Vratislav Podzimek <vpodzime 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..00f3c0c
--- /dev/null
+++ b/blivet/osinstall.py
@@ -0,0 +1,1035 @@
+#
+# Copyright (C) 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): Vratislav Podzimek <vpodzime at redhat.com>
+#
+
+"""This module provides functions related to OS installation."""
+
+import shlex
+import os
+import stat
+import time
+
+from . import util
+from . import flags
+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
+from .formats import get_device_format_class
+from .devicelibs.dm import name_from_dm_node
+from .formats import getFormat
+
+from .i18n import _
+
+import logging
+log = logging.getLogger("blivet")
+
+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)
+
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.
 
-- 
2.1.0



More information about the anaconda-patches mailing list