diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index 971c0dc..fac9ee8 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -620,6 +620,7 @@ class BootLoader(object):
         return valid
 
     def set_stage1_device(self, devices):
+        self.stage1_device = None
         if not self.stage1_disk:
             raise BootLoaderError("need stage1 disk to set stage1 device")
 
@@ -627,7 +628,6 @@ class BootLoader(object):
             self.stage1_device = self.stage2_device
             return
 
-        self.stage1_device = None
         for device in devices:
             if self.stage1_disk not in device.disks:
                 continue
diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
index 1baf6bd..c2a4b2b 100644
--- a/pyanaconda/constants.py
+++ b/pyanaconda/constants.py
@@ -49,6 +49,11 @@ productArch = product.productArch
 bugzillaUrl = product.bugUrl
 isFinal = product.isFinal
 
+# for use in device names, eg: "fedora", "rhel"
+shortProductName = productName.lower()
+if productName.count(" "):
+    shortProductName = ''.join(s[0] for s in shortProductName.split())
+
 exceptionText = _("An unhandled exception has occurred.  This "
                   "is most likely a bug.  Please save a copy of "
                   "the detailed exception and file a bug report")
diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py
index 17a0c11..3f7bc67 100644
--- a/pyanaconda/installclass.py
+++ b/pyanaconda/installclass.py
@@ -104,7 +104,7 @@ class BaseInstallClass(object):
         # provided as a way for other products to specify their own.
         return None
 
-    def setDefaultPartitioning(self, storage, platform):
+    def setDefaultPartitioning(self, storage):
         autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType,
                                  size=1024, maxSize=50*1024, grow=True,
                                  btr=True, lv=True, encrypted=True),
@@ -112,14 +112,21 @@ class BaseInstallClass(object):
                                  size=500, grow=True, requiredSpace=50*1024,
                                  btr=True, lv=True, encrypted=True)]
 
-        bootreq = platform.setDefaultPartitioning()
-        if bootreq:
-            autorequests.extend(bootreq)
+        bootreqs = storage.platform.setDefaultPartitioning()
+        if bootreqs:
+            autorequests.extend(bootreqs)
 
         (minswap, maxswap) = iutil.swapSuggestion()
         autorequests.append(PartSpec(fstype="swap", size=minswap, maxSize=maxswap,
                                      grow=True, lv=True, encrypted=True))
 
+        for autoreq in autorequests:
+            if autoreq.fstype is None:
+                if autoreq.mountpoint == "/boot":
+                    autoreq.fstype = storage.defaultBootFSType
+                else:
+                    autoreq.fstype = storage.defaultFSType
+
         storage.autoPartitionRequests = autorequests
 
     def configure(self, anaconda):
diff --git a/pyanaconda/installclasses/fedora.py b/pyanaconda/installclasses/fedora.py
index e2c1543..ae1abf0 100644
--- a/pyanaconda/installclasses/fedora.py
+++ b/pyanaconda/installclasses/fedora.py
@@ -76,9 +76,7 @@ class InstallClass(BaseInstallClass):
 
     def configure(self, anaconda):
 	BaseInstallClass.configure(self, anaconda)
-        BaseInstallClass.setDefaultPartitioning(self,
-                                                anaconda.storage,
-                                                anaconda.platform)
+        BaseInstallClass.setDefaultPartitioning(self, anaconda.storage)
 
     def setGroupSelection(self, anaconda):
         BaseInstallClass.setGroupSelection(self, anaconda)
diff --git a/pyanaconda/installclasses/rhel.py b/pyanaconda/installclasses/rhel.py
index 339b5b8..0e36d26 100644
--- a/pyanaconda/installclasses/rhel.py
+++ b/pyanaconda/installclasses/rhel.py
@@ -55,9 +55,7 @@ class InstallClass(BaseInstallClass):
 
     def configure(self, anaconda):
         BaseInstallClass.configure(self, anaconda)
-        BaseInstallClass.setDefaultPartitioning(self,
-                                                anaconda.storage,
-                                                anaconda.platform)
+        BaseInstallClass.setDefaultPartitioning(self, anaconda.storage)
 
     def productMatches(self, oldprod):
         if oldprod is None:
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index 7e2022a..7749da1 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -25,6 +25,7 @@ from storage.devicelibs.lvm import getPossiblePhysicalExtents
 from storage.devicelibs.mpath import MultipathConfigWriter, MultipathTopology
 from storage.formats import getFormat
 from storage.partitioning import doPartitioning
+from storage.partitioning import growLVM
 import storage.iscsi
 import storage.fcoe
 import storage.zfcp
@@ -234,7 +235,6 @@ class Authconfig(commands.authconfig.FC3_Authconfig):
 
 class AutoPart(commands.autopart.F17_AutoPart):
     def execute(self, storage, ksdata, instClass):
-        from pyanaconda.platform import getPlatform
         from pyanaconda.storage.partitioning import doAutoPartition
 
         if not self.autopart:
@@ -242,7 +242,7 @@ class AutoPart(commands.autopart.F17_AutoPart):
 
         # sets up default autopartitioning.  use clearpart separately
         # if you want it
-        instClass.setDefaultPartitioning(storage, getPlatform())
+        instClass.setDefaultPartitioning(storage)
         storage.doAutoPart = True
 
         if self.encrypted:
@@ -537,6 +537,9 @@ class LogVol(commands.logvol.F17_LogVol):
         for l in self.lvList:
             l.execute(storage, ksdata, instClass)
 
+        if self.lvList:
+            growLVM(storage)
+
 class LogVolData(commands.logvol.F17_LogVolData):
     def execute(self, storage, ksdata, instClass):
         devicetree = storage.devicetree
@@ -843,6 +846,9 @@ class Partition(commands.partition.F17_Partition):
         for p in self.partitions:
             p.execute(storage, ksdata, instClass)
 
+        if self.partitions:
+            doPartitioning(storage)
+
 class PartitionData(commands.partition.F17_PartData):
     def execute(self, storage, ksdata, instClass):
         devicetree = storage.devicetree
@@ -1031,13 +1037,6 @@ class PartitionData(commands.partition.F17_PartData):
             except KeyError:
                 pass
 
-            if "format" in kwargs:
-                # set weight based on fstype and mountpoint
-                mpt = getattr(kwargs["format"], "mountpoint", None)
-                fstype = kwargs["format"].type
-                kwargs["weight"] = storage.platform.weight(fstype=fstype,
-                                                           mountpoint=mpt)
-
             request = storage.newPartition(**kwargs)
             storage.createDevice(request)
 
@@ -1579,5 +1578,4 @@ def doKickstartStorage(storage, ksdata, instClass):
     ksdata.logvol.execute(storage, ksdata, instClass)
     ksdata.btrfs.execute(storage, ksdata, instClass)
     storage.setUpBootLoader()
-    doPartitioning(storage)
 
diff --git a/pyanaconda/platform.py b/pyanaconda/platform.py
index 9abf0f5..7eb05af 100644
--- a/pyanaconda/platform.py
+++ b/pyanaconda/platform.py
@@ -310,8 +310,6 @@ class S390(Platform):
         """Return the default platform-specific partitioning information."""
         from storage.partspec import PartSpec
         return [PartSpec(mountpoint="/boot", size=500,
-#TODO: need to pass this info in w/o using anaconda object
-#                        fstype=self.anaconda.storage.defaultBootFSType,
                          weight=self.weight(mountpoint="/boot"), lv=True,
                          singlePV=True)]
 
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py
index 698c4fd..0c1b10a 100644
--- a/pyanaconda/storage/__init__.py
+++ b/pyanaconda/storage/__init__.py
@@ -26,6 +26,7 @@ import stat
 import errno
 import sys
 import statvfs
+import copy
 
 import nss.nss
 import parted
@@ -37,6 +38,7 @@ from pykickstart.constants import *
 from pyanaconda.flags import flags
 from pyanaconda import tsort
 from pyanaconda.errors import *
+from pyanaconda.bootloader import BootLoaderError
 
 from errors import *
 from devices import *
@@ -49,6 +51,11 @@ from devicelibs.dm import name_from_dm_node
 from devicelibs.crypto import generateBackupPassphrase
 from devicelibs.mpath import MultipathConfigWriter
 from devicelibs.edd import get_edd_dict
+from devicelibs.mdraid import get_member_space
+from devicelibs.lvm import get_pv_space
+from .partitioning import SameSizeSet
+from .partitioning import TotalSizeSet
+from .partitioning import doPartitioning
 from udev import *
 import iscsi
 import fcoe
@@ -347,10 +354,10 @@ class Storage(object):
         self.autoPartAddBackupPassphrase = False
         self.encryptionRetrofit = False
         self.autoPartitionRequests = []
-        self.shrinkPartitions = {}
         self.eddDict = {}
 
         self.__luksDevs = {}
+        self.size_sets = []
 
         self.iscsi = iscsi.iscsi()
         self.fcoe = fcoe.fcoe()
@@ -1085,6 +1092,15 @@ class Storage(object):
         else:
             name = "req%d" % self.nextID
 
+        if "weight" not in kwargs:
+            fmt = kwargs.get("format")
+            if fmt:
+                mountpoint = getattr(fmt, "mountpoint", None)
+
+                kwargs["weight"] = self.platform.weight(mountpoint=mountpoint,
+                                                        fstype=fmt.type)
+
+
         return PartitionDevice(name, *args, **kwargs)
 
     def newMDArray(self, *args, **kwargs):
@@ -1092,7 +1108,8 @@ class Storage(object):
         if kwargs.has_key("fmt_type"):
             kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
                                          mountpoint=kwargs.pop("mountpoint",
-                                                               None))
+                                                               None),
+                                         **kwargs.pop("fmt_args", {}))
 
         if kwargs.has_key("minor"):
             kwargs["minor"] = int(kwargs["minor"])
@@ -1123,7 +1140,7 @@ class Storage(object):
                         hostname = nd.hostname
                         break
 
-            name = self.suggestContainerName(hostname=hostname, prefix="vg")
+            name = self.suggestContainerName(hostname=hostname)
 
         if name in self.names:
             raise ValueError("name already in use")
@@ -1136,7 +1153,8 @@ class Storage(object):
         mountpoint = kwargs.pop("mountpoint", None)
         if kwargs.has_key("fmt_type"):
             kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
-                                         mountpoint=mountpoint)
+                                         mountpoint=mountpoint,
+                                         **kwargs.pop("fmt_args", {}))
 
         if kwargs.has_key("name"):
             name = kwargs.pop("name")
@@ -1145,8 +1163,7 @@ class Storage(object):
                 swap = True
             else:
                 swap = False
-            name = self.suggestDeviceName(prefix="lv",
-                                          parent=vg,
+            name = self.suggestDeviceName(parent=vg,
                                           swap=swap,
                                           mountpoint=mountpoint)
 
@@ -1163,7 +1180,9 @@ class Storage(object):
             name = args[0]
 
         mountpoint = kwargs.pop("mountpoint", None)
-        fmt_kwargs = {"mountpoint": mountpoint}
+
+        fmt_args = kwargs.pop("fmt_args", {})
+        fmt_args.update({"mountpoint": mountpoint})
 
         if kwargs.pop("subvol", False):
             dev_class = BTRFSSubVolumeDevice
@@ -1178,7 +1197,7 @@ class Storage(object):
                 # for btrfs this only needs to ensure the subvol name is not
                 # already in use within the parent volume
                 name = self.suggestDeviceName(mountpoint=mountpoint)
-            fmt_kwargs["mountopts"] = "subvol=%s" % name
+            fmt_args["mountopts"] = "subvol=%s" % name
             kwargs.pop("metaDataLevel", None)
             kwargs.pop("dataLevel", None)
         else:
@@ -1192,25 +1211,22 @@ class Storage(object):
                             hostname = nd.hostname
                             break
 
-                name = self.suggestContainerName(prefix="btrfs",
-                                                 hostname=hostname)
-            #if name and name.startswith("btrfs_"):
-            #    fmt_kwargs["label"] = name[6:]
-            #elif name and not name.startswith("btrfs"):
-            fmt_kwargs["label"] = name
-
-        # do we want to prevent a subvol with a name that matches another dev?
-        if name in self.names:
-            raise ValueError("name already in use")
+                name = self.suggestContainerName(hostname=hostname)
+            if "label" not in fmt_args:
+                fmt_args["label"] = name
 
         # discard fmt_type since it's btrfs always
         kwargs.pop("fmt_type", None)
 
         # this is to avoid auto-scheduled format create actions
         device = dev_class(name, **kwargs)
-        device.format = getFormat("btrfs", **fmt_kwargs)
+        device.format = getFormat("btrfs", **fmt_args)
         return device
 
+    def newBTRFSSubVolume(self, *args, **kwargs):
+        kwargs["subvol"] = True
+        return self.newBTRFS(*args, **kwargs)
+
     def createDevice(self, device):
         """ Schedule creation of a device.
 
@@ -1301,15 +1317,19 @@ class Storage(object):
 
     def suggestContainerName(self, hostname=None, prefix=""):
         """ Return a reasonable, unused device name. """
+        if not prefix:
+            prefix = shortProductName
+
         # try to create a device name incorporating the hostname
         if hostname not in (None, "", 'localhost', 'localhost.localdomain'):
             template = "%s_%s" % (prefix, hostname.split('.')[0].lower())
             template = self.safeDeviceName(template)
-        elif flags.imageInstall:
-            template = "%s_image" % prefix
         else:
             template = prefix
 
+        if flags.imageInstall:
+            template = "%s_image" % template
+
         names = self.names
         name = template
         if name in names:
@@ -1333,11 +1353,14 @@ class Storage(object):
         body = ""
         if mountpoint:
             if mountpoint == "/":
-                body = "_root"
+                body = "root"
             else:
-                body = mountpoint.replace("/", "_")
+                body = mountpoint[1:].replace("/", "_")
         elif swap:
-            body = "_swap"
+            body = "swap"
+
+        if prefix:
+            body = "_" + body
 
         template = self.safeDeviceName(prefix + body)
         names = self.names
@@ -1352,9 +1375,6 @@ class Storage(object):
         # also include names of any lvs in the parent for the case of the
         # temporary vg in the lvm dialogs, which can contain lvs that are
         # not yet in the devicetree and therefore not in self.names
-        if hasattr(parent, "lvs"):
-            names.extend([full_name(d.lvname, parent) for d in parent.lvs])
-
         if full_name(name, parent) in names or not body:
             for i in range(100):
                 name = "%s%02d" % (template, i)
@@ -1727,7 +1747,10 @@ class Storage(object):
 
         self.bootloader.stage1_disk = self.devicetree.resolveDevice(self.data.bootloader.bootDrive)
         self.bootloader.stage2_device = self.bootDevice
-        self.bootloader.set_stage1_device(self.devices)
+        try:
+            self.bootloader.set_stage1_device(self.devices)
+        except BootLoaderError as e:
+            log.debug("failed to set bootloader stage1 device: %s" % e)
 
     @property
     def bootDisk(self):
@@ -1745,6 +1768,10 @@ class Storage(object):
         return dev
 
     @property
+    def bootLoaderDevice(self):
+        return getattr(self.bootloader, "stage1_device", None)
+
+    @property
     def bootFSTypes(self):
         """A list of all valid filesystem types for the boot partition."""
         fstypes = []
@@ -1842,6 +1869,249 @@ class Storage(object):
 
         return 0
 
+    def getFSType(self, mountpoint=None):
+        """ Return the default filesystem type based on mountpoint. """
+        fstype = self.defaultFSType
+        if not mountpoint:
+            # just return the default
+            pass
+        elif mountpoint.lower() == "swap":
+            fstype = "swap"
+        elif mountpoint == "/boot":
+            fstype = self.defaultBootFSType
+        elif mountpoint == "/boot/efi":
+            if iutil.isMactel():
+                fstype = "hfs+"
+            else:
+                fstype = "efi"
+
+        return fstype
+
+    def setContainerMembers(self, container, factory):
+        # set up member devices
+        container_size = 0
+        if container:
+            log.debug("using container %s with %d devices" % (container.name,
+                                len(self.devicetree.getChildren(container))))
+            container_size = factory.container_size_func(container)
+            log.debug("raw container size reported as %d" % container_size)
+            disks = list(set([d for m in container.parents for d in m.disks]))
+            disks.sort(key=lambda d: d.name, cmp=self.compareDisks)
+            factory.disks = disks
+
+        device_space = factory.device_size
+        log.debug("device requires %d" % device_space)
+        container_size += device_space
+
+        # XXX TODO: multiple member devices per disk
+
+        # XXX Never try to reuse member devices. They can be converted by the
+        #     user into free space.
+        if container:
+            members = container.parents[:]
+            for member in members[:]:
+                if isinstance(member, LUKSDevice):
+                    member = member.slave
+
+                member.req_base_size = PartitionDevice.defaultSize
+                member.req_size = member.req_base_size
+                member.req_grow = True
+
+                for ss in self.size_sets[:]:
+                    if member in ss.devices:
+                        self.size_sets.remove(ss)
+        else:
+            # set up members as needed to accommodate the device
+            members = []
+            for disk in factory.disks:
+                if factory.encrypted:
+                    luks_format = factory.member_format
+                    member_format = "luks"
+                else:
+                    member_format = factory.member_format
+
+                member = self.newPartition(parents=[disk], grow=True,
+                                           fmt_type=member_format)
+                self.createDevice(member)
+                if factory.encrypted:
+                    fmt = getFormat(luks_format)
+                    member = LUKSDevice("luks-%s" % member.name,
+                                        parents=[member], format=fmt)
+                    self.createDevice(member)
+
+                members.append(member)
+
+        log.debug("adding a %s with size %d" % (factory.set_class.__name__,
+                                                container_size))
+        size_set = factory.set_class(members, container_size)
+        self.size_sets.append(size_set)
+
+        try:
+            doPartitioning(self)
+        except StorageError as e:
+            log.error("UI: failed to allocate partitions: %s" % e)
+            for device in members:
+                actions = self.devicetree.findActions(device=device)
+                for a in reversed(actions):
+                    self.devicetree.cancelAction(a)
+            # FIXME: revert container to its original state?
+            return
+        else:
+            # fix the sizes of all allocated partitions
+            for partition in self.partitions:
+                partition.req_grow = False
+                partition.req_base_size = partition.size
+                partition.req_size = partition.size
+
+        return members
+
+    def getDeviceFactory(self, device_type, size, **kwargs):
+        disks = kwargs.get("disks", [])
+        encrypted = kwargs.get("encrypted", False)
+
+        # md, btrfs, lvm
+        raid_level = kwargs.get("level")
+
+        class_table = {AUTOPART_TYPE_LVM: LVMFactory,
+                       AUTOPART_TYPE_BTRFS: BTRFSFactory}
+
+        factory_class = class_table.get(device_type, MDFactory)
+        log.debug("instantiating %s: %r, %s, %s, %s" % (factory_class,
+                    self, size, [d.name for d in disks], raid_level))
+        return factory_class(self, size, disks, raid_level, encrypted)
+
+    def newDevice(self, device_type, size, **kwargs):
+        """ Schedule creation of a device based on a top-down specification.
+
+            Arguments:
+
+                device_type         an AUTOPART_TYPE constant (lvm|btrfs|plain)
+                size                device's requested size
+
+            Keyword arguments:
+
+                mountpoint          the device's mountpoint
+                fstype              the device's filesystem type, or swap
+                label               filesystem label
+                disks               the set of disks we can allocate from
+                encrypted           boolean
+
+                level               (btrfs/md only) RAID level
+
+                striped             (lvm only) boolean
+                mirrored            (lvm only) boolean
+
+        """
+        mountpoint = kwargs.get("mountpoint")
+        fstype = kwargs.get("fstype")
+        label = kwargs.get("label")
+        disks = kwargs.get("disks")
+        encrypted = kwargs.get("encrypted", self.data.autopart.encrypted)
+
+        # md, btrfs
+        raid_level = kwargs.get("level")
+
+        # lvm
+        striped = kwargs.get("striped")
+        mirrored = kwargs.get("mirrored")
+
+        if not fstype:
+            fstype = self.getFSType(mountpoint=mountpoint)
+            if fstype == "swap":
+                mountpoint = None
+
+        if fstype == "swap" and device_type == AUTOPART_TYPE_BTRFS:
+            device_type = AUTOPART_TYPE_PLAIN
+        elif device_type != AUTOPART_TYPE_PLAIN and \
+             mountpoint and mountpoint.startswith("/boot"):
+            device_type = AUTOPART_TYPE_PLAIN
+
+        fmt_args = {}
+        if label:
+            fmt_args["label"] = label
+
+        # TODO: striping, mirroring, &c
+        # TODO: non-partition members (pv-on-md)
+        if device_type == AUTOPART_TYPE_PLAIN:
+            device = self.newPartition(grow=True, maxsize=size,
+                                       fmt_type=fstype, mountpoint=mountpoint,
+                                       fmt_args=fmt_args)
+            self.createDevice(device)
+            try:
+                doPartitioning(self)
+            except StorageError as e:
+                log.error("failed to allocate partitions: %s" % e)
+                actions = self.devicetree.findActions(device=device)
+                for a in reversed(actions):
+                    self.devicetree.cancelAction(a)
+            else:
+                # fix the sizes of all allocated partitions
+                for partition in self.partitions:
+                    partition.req_grow = False
+                    partition.req_base_size = partition.size
+                    partition.req_size = partition.size
+
+            return
+
+        factory = self.getDeviceFactory(device_type, size, **kwargs)
+
+        for p in self.partitions:
+            log.debug("%r" % p)
+
+        container = None
+        containers = [c for c in factory.container_list if not c.exists]
+        if containers:
+            container = containers[0]
+
+        parents = self.setContainerMembers(container, factory)
+
+        # set up container
+        if not container and factory.new_container_attr:
+            log.debug("creating new container")
+            container = factory.new_container(parents=parents)
+            self.createDevice(container)
+
+        if container:
+            parents = [container]
+            log.debug("%r" % container)
+
+        # add the new device to the container
+        if factory.new_device_attr:
+            free = getattr(container, "freeSpace", size)
+            if free < size:
+                log.info("adjusting device size from %.2f to %.2f so it fits "
+                         "in container" % (size, free))
+                size = free
+
+            log.debug("creating new device")
+            device = factory.new_device(parents=parents,
+                                        size=size,
+                                        fmt_type=fstype,
+                                        mountpoint=mountpoint,
+                                        fmt_args=fmt_args)
+            self.createDevice(device)
+
+    def copy(self):
+        new = copy.deepcopy(self)
+        # go through and re-get partedPartitions from the disks since they
+        # don't get deep-copied
+        for partition in new.partitions:
+            if not partition._partedPartition:
+                continue
+
+            # don't ask me why, but we have to update the refs in req_disks
+            req_disks = []
+            for disk in partition.req_disks:
+                req_disks.append(new.devicetree.getDeviceByID(disk.id))
+
+            partition.req_disks = req_disks
+
+            p = partition.disk.format.partedDisk.getPartitionByPath(partition.path)
+            partition.partedPartition = p
+
+        return new
+
+
 def mountExistingSystem(fsset, rootEnt,
                         allowDirty=None, dirtyCB=None,
                         readOnly=None):
@@ -2784,3 +3054,112 @@ def parseFSTab(devicetree, chroot=None):
 
     return (mounts, swaps)
 
+
+class DeviceFactory(object):
+    type_desc = None
+    member_format = None
+    new_container_attr = None
+    new_device_attr = None
+    container_list_attr = None
+
+    def __init__(self, storage, size, disks, raid_level, encrypted):
+        self.storage = storage
+        self.size = size
+        self.disks = disks
+        self.raid_level = raid_level
+        self.encrypted = encrypted
+
+        self.size_func_kwargs = {}
+
+        if raid_level in ("raid1", "raid10"):
+            self.set_class = SameSizeSet
+        else:
+            self.set_class = TotalSizeSet
+
+    @property
+    def container_list(self):
+        return getattr(self.storage, self.container_list_attr)
+
+    def new_container(self, *args, **kwargs):
+        return getattr(self.storage, self.new_container_attr)(*args, **kwargs)
+
+    def new_device(self, *args, **kwargs):
+        return getattr(self.storage, self.new_device_attr)(*args, **kwargs)
+
+    def container_size_func(self, container):
+        return container.size
+
+    @property
+    def device_size(self):
+        return self.size
+
+
+class BTRFSFactory(DeviceFactory):
+    type_desc = "btrfs"
+    member_format = "btrfs"
+    new_container_attr = "newBTRFS"
+    new_device_attr = "newBTRFSSubVolume"
+    container_list_attr = "btrfsVolumes"
+
+    def __init__(self, storage, size, disks, raid_level, encrypted):
+        super(BTRFSFactory, self).__init__(storage, size, disks, raid_level,
+                                           encrypted)
+        self.raid_level = raid_level or "single"
+
+    @property
+    def device_size(self):
+        # until we get/need something better
+        if self.raid_level in ("single", "raid0"):
+            return self.size
+        elif self.raid_level in ("raid1", "raid10"):
+            return self.size * len(self.disks)
+
+    def container_size_func(self, container):
+        if container.exists:
+            size = container.size
+        else:
+            size = sum([s._size for s in container.subvolumes])
+
+        return size
+
+class LVMFactory(DeviceFactory):
+    type_desc = "lvm"
+    member_format = "lvmpv"
+    new_container_attr = "newVG"
+    new_device_attr = "newLV"
+    container_list_attr = "vgs"
+
+    def __init__(self, storage, size, disks, raid_level, encrypted):
+        super(LVMFactory, self).__init__(storage, size, disks, raid_level,
+                                         encrypted)
+        if raid_level in ("raid1", "raid10"):
+            self.size_func_kwargs["mirrored"] = True
+        if raid_level in ("raid0", "raid10"):
+            self.size_func_kwargs["striped"] = True
+
+    @property
+    def device_size(self):
+        return get_pv_space(self.size, len(self.disks), **self.size_func_kwargs)
+
+    def container_size_func(self, container):
+        return container.size - container.freeSpace
+
+class MDFactory(DeviceFactory):
+    type_desc = "md"
+    member_format = "mdmember"
+    new_container_attr = None
+    new_device_attr = "newMD"
+
+    def __init__(self, storage, size, disks, raid_level, encrypted):
+        super(MDFactory, self).__init__(storage, size, disks, raid_level,
+                                        encrypted)
+        self.size_func_kwargs = {"level": raid_level}
+
+    @property
+    def container_list(self):
+        return []
+
+    @property
+    def device_size(self):
+        return get_member_space(self.size, len(self.disks),
+                                **self.size_func_kwargs)
diff --git a/pyanaconda/storage/devicelibs/btrfs.py b/pyanaconda/storage/devicelibs/btrfs.py
index 3fe1218..f114ba4 100644
--- a/pyanaconda/storage/devicelibs/btrfs.py
+++ b/pyanaconda/storage/devicelibs/btrfs.py
@@ -32,24 +32,21 @@ _ = lambda x: gettext.ldgettext("anaconda", x)
 import logging
 log = logging.getLogger("storage")
 
-def btrfs(args, progress=None, capture=False):
+def btrfs(args, capture=False):
     kwargs = {}
     kwargs["stderr"] = "/dev/tty5"
     if capture:
-        # execWithCapture can't take a progress window so it gets ignored
         exec_func = iutil.execWithCapture
     else:
-        exec_func = iutil.execWithPulseProgress
+        exec_func = iutil.execWithRedirect
         kwargs["stdout"] = "/dev/tty5"
-        kwargs["progress"] = progress
 
     ret = exec_func("btrfs", args, **kwargs)
-    if not capture and ret.rc:
-        raise BTRFSError(ret.stderr)
-
+    if ret:
+        raise BTRFSError(ret)
     return ret
 
-def create_volume(devices, label=None, data=None, metadata=None, progress=None):
+def create_volume(devices, label=None, data=None, metadata=None):
     """ For now, data and metadata must be strings mkfs.btrfs understands. """
     if not devices:
         raise ValueError("no devices specified")
@@ -68,11 +65,10 @@ def create_volume(devices, label=None, data=None, metadata=None, progress=None):
 
     args.extend(devices)
 
-    ret = iutil.execWithPulseProgress("mkfs.btrfs", args,
-                                      stdout="/dev/tty5", stderr="/dev/tty5",
-                                      progress=progress)
-    if ret.rc:
-        raise BTRFSError(ret.stderr)
+    ret = iutil.execWithRedirect("mkfs.btrfs", args,
+                                 stdout="/dev/tty5", stderr="/dev/tty5")
+    if ret:
+        raise BTRFSError(ret)
 
     return ret
 
@@ -82,21 +78,21 @@ def create_volume(devices, label=None, data=None, metadata=None, progress=None):
 
 # remove device
 
-def create_subvolume(mountpoint, name, progress=None):
+def create_subvolume(mountpoint, name):
     if not os.path.ismount(mountpoint):
         raise ValueError("volume not mounted")
 
     path = os.path.normpath("%s/%s" % (mountpoint, name))
     args = ["subvol", "create", path]
-    return btrfs(args, progress=progress)
+    return btrfs(args)
 
-def delete_subvolume(mountpoint, name, progress=None):
+def delete_subvolume(mountpoint, name):
     if not os.path.ismount(mountpoint):
         raise ValueError("volume not mounted")
 
     path = os.path.normpath("%s/%s" % (mountpoint, name))
     args = ["subvol", "delete", path]
-    return btrfs(args, progress=progress)
+    return btrfs(args)
 
 def create_snapshot(source, dest):
     pass
diff --git a/pyanaconda/storage/devicelibs/lvm.py b/pyanaconda/storage/devicelibs/lvm.py
index 136168b..fedba99 100644
--- a/pyanaconda/storage/devicelibs/lvm.py
+++ b/pyanaconda/storage/devicelibs/lvm.py
@@ -36,6 +36,10 @@ _ = lambda x: gettext.ldgettext("anaconda", x)
 
 MAX_LV_SLOTS = 256
 
+# some of lvm's defaults that we have no way to ask it for
+LVM_PE_START = 1.0      # MB
+LVM_PE_SIZE = 4.0       # MB
+
 def has_lvm():
     if iutil.find_program_in_path("lvm"):
         for line in open("/proc/devices").readlines():
@@ -143,6 +147,17 @@ def clampSize(size, pesize, roundup=None):
 
     return long(round(float(size)/float(pesize)) * pesize)
 
+def get_pv_space(size, disks, pesize=LVM_PE_SIZE,
+                 striped=False, mirrored=False):
+    """ Given specs for an LV, return total PV space required. """
+    # XXX default extent size should be something we can ask of lvm
+    # TODO: handle striped and mirrored
+    # this is adding one extent for the lv's metadata
+    space = clampSize(size, pesize, roundup=True) + \
+            (LVM_PE_START * disks) + \
+            pesize
+    return space
+
 def lvm(args):
     ret = iutil.execWithRedirect("lvm", args,
                                  stdout = "/dev/tty5",
diff --git a/pyanaconda/storage/devicelibs/mdraid.py b/pyanaconda/storage/devicelibs/mdraid.py
index e383bdf..d0b8594 100644
--- a/pyanaconda/storage/devicelibs/mdraid.py
+++ b/pyanaconda/storage/devicelibs/mdraid.py
@@ -31,6 +31,10 @@ _ = lambda x: gettext.ldgettext("anaconda", x)
 import logging
 log = logging.getLogger("storage")
 
+# these defaults were determined empirically
+MD_SUPERBLOCK_SIZE = 2.0    # MB
+MD_CHUNK_SIZE = 0.5         # MB
+
 # raidlevels constants
 RAID10 = 10
 RAID6 = 6
@@ -124,6 +128,40 @@ def get_raid_max_spares(raidlevel, nummembers):
 
     raise ValueError, "invalid raid level %d" % raidlevel
 
+def get_member_space(size, disks, level=None):
+    space = 0   # size of *each* member device
+
+    if isinstance(level, str):
+        level = raidLevel(level)
+
+    min_members = get_raid_min_members(level)
+    if disks < min_members:
+        raise ValueError("raid%d requires at least %d disks"
+                         % (level, min_members))
+
+    if level == RAID0:
+        # you need the sum of the member sizes to equal your desired capacity
+        space = size / disks
+    elif level == RAID1:
+        # you need each member's size to equal your desired capacity
+        space = size
+    elif level in (RAID4, RAID5):
+        # you need the sum of all but one members' sizes to equal your desired
+        # capacity
+        space = size / (disks - 1)
+    elif level == RAID6:
+        # you need the sum of all but two members' sizes to equal your desired
+        # capacity
+        space = size / (disks - 2)
+    elif level == RAID10:
+        # you need the sum of the member sizes to equal twice your desired
+        # capacity
+        space = size / (disks / 2.0)
+
+    space += MD_SUPERBLOCK_SIZE
+
+    return space * disks
+
 def mdadm(args):
     ret = iutil.execWithRedirect("mdadm", args,
                                  stdout = "/dev/tty5",
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
index c99c3c2..795ff49 100644
--- a/pyanaconda/storage/devices.py
+++ b/pyanaconda/storage/devices.py
@@ -2023,7 +2023,7 @@ class LVMVolumeGroupDevice(DMDevice):
 
         # TODO: validate peSize if given
         if not self.peSize:
-            self.peSize = 32.0   # MB
+            self.peSize = lvm.LVM_PE_SIZE  # MB
 
         if not self.exists:
             self.pvCount = len(self.parents)
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py
index 4d317c9..1567305 100644
--- a/pyanaconda/storage/devicetree.py
+++ b/pyanaconda/storage/devicetree.py
@@ -1728,7 +1728,7 @@ class DeviceTree(object):
                                                             hidden.id))
                 self._hidden.remove(hidden)
                 self._devices.append(hidden)
-                lvm.lvm_cc_removeFilterRejectRegexp(device.name)
+                lvm.lvm_cc_removeFilterRejectRegexp(hidden.name)
 
     def _setupLvs(self):
         ret = False
@@ -2078,6 +2078,11 @@ class DeviceTree(object):
     def getDevicesByInstance(self, device_class):
         return [d for d in self._devices if isinstance(d, device_class)]
 
+    def getDeviceByID(self, id_num):
+        for device in self._devices:
+            if device.id == id_num:
+                return device
+
     @property
     def devices(self):
         """ List of device instances """
diff --git a/pyanaconda/storage/formats/disklabel.py b/pyanaconda/storage/formats/disklabel.py
index 338a880..87dbea2 100644
--- a/pyanaconda/storage/formats/disklabel.py
+++ b/pyanaconda/storage/formats/disklabel.py
@@ -85,10 +85,13 @@ class DiskLabel(DeviceFormat):
         """
         new = self.__class__.__new__(self.__class__)
         memo[id(self)] = new
-        shallow_copy_attrs = ('_partedDevice', '_partedDisk', '_origPartedDisk')
+        shallow_copy_attrs = ('_partedDevice', '_alignment', '_endAlignment')
+        duplicate_attrs = ('_partedDisk', '_origPartedDisk')
         for (attr, value) in self.__dict__.items():
             if attr in shallow_copy_attrs:
                 setattr(new, attr, copy.copy(value))
+            elif attr in duplicate_attrs:
+                setattr(new, attr, value.duplicate())
             else:
                 setattr(new, attr, copy.deepcopy(value, memo))
 
diff --git a/pyanaconda/storage/formats/lvmpv.py b/pyanaconda/storage/formats/lvmpv.py
index 994561e..99aadb7 100644
--- a/pyanaconda/storage/formats/lvmpv.py
+++ b/pyanaconda/storage/formats/lvmpv.py
@@ -65,7 +65,7 @@ class LVMPhysicalVolume(DeviceFormat):
         self.vgUuid = kwargs.get("vgUuid")
         # liblvm may be able to tell us this at some point, even
         # for not-yet-created devices
-        self.peStart = kwargs.get("peStart", 1.0)    # in MB
+        self.peStart = kwargs.get("peStart", lvm.LVM_PE_START)    # in MB
 
         self.inconsistentVG = False
 
diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py
index e9c06f6..7f34b85 100644
--- a/pyanaconda/storage/partitioning.py
+++ b/pyanaconda/storage/partitioning.py
@@ -131,12 +131,6 @@ def _schedulePartitions(storage, disks):
         if request.requiredSpace and request.requiredSpace > free:
             continue
 
-        if request.fstype is None:
-            if request.mountpoint == "/boot":
-                request.fstype = storage.defaultBootFSType
-            else:
-                request.fstype = storage.defaultFSType
-
         elif request.fstype in ("prepboot", "efi", "biosboot", "hfs+") and \
              stage1_device:
             # there should never be a need for more than one of these
@@ -263,6 +257,7 @@ def _scheduleVolumes(storage, devs):
                            "singlePV": request.singlePV})
         else:
             kwargs.update({"parents": [container],
+                           "size": request.size,
                            "subvol": True})
 
         dev = new_volume(**kwargs)
@@ -270,19 +265,6 @@ def _scheduleVolumes(storage, devs):
         # schedule the device for creation
         storage.createDevice(dev)
 
-def scheduleShrinkActions(storage):
-    """ Schedule actions to shrink partitions as per autopart selection. """
-    for (path, size) in storage.shrinkPartitions.items():
-        device = storage.devicetree.getDeviceByPath(path, preferLeaves=False)
-        if not device or not isinstance(device, PartitionDevice):
-            raise StorageError("device %s scheduled for shrink disappeared"
-                                % path)
-        storage.devicetree.registerAction(ActionResizeFormat(device, size))
-        storage.devicetree.registerAction(ActionResizeDevice(device, size))
-        # aligning the partition's new end sector may have changed the size
-        if device.targetSize != size:
-            device.format.targetSize = device.targetSize
-
 def doAutoPartition(storage, data):
     log.debug("doAutoPart: %s" % storage.doAutoPart)
     log.debug("encryptedAutoPart: %s" % storage.encryptedAutoPart)
@@ -299,9 +281,6 @@ def doAutoPartition(storage, data):
     devs = []
 
     if storage.doAutoPart:
-        # XXX this doesn't belong on newui
-        scheduleShrinkActions(storage)
-
         disks = _getCandidateDisks(storage)
         devs = _scheduleImplicitPartitions(storage, disks)
         log.debug("candidate disks: %s" % disks)
@@ -321,6 +300,8 @@ def doAutoPartition(storage, data):
 
         # grow LVs
         growLVM(storage)
+
+        growBTRFS(storage)
     except PartitioningWarning as e:
         log.warning(str(e))
         if errorHandler.cb(e) == ERROR_RAISE:
@@ -768,8 +749,7 @@ def doPartitioning(storage):
     for part in storage.partitions:
         part.req_bootable = False
 
-        if part.exists or \
-           (storage.deviceImmutable(part) and part.partedPartition):
+        if part.exists:
             # if the partition is preexisting or part of a complex device
             # then we shouldn't modify it
             partitions.remove(part)
@@ -789,7 +769,7 @@ def doPartitioning(storage):
     free = getFreeRegions(disks)
     try:
         allocatePartitions(storage, disks, partitions, free)
-        growPartitions(disks, partitions, free)
+        growPartitions(disks, partitions, free, size_sets=storage.size_sets)
     finally:
         # The number and thus the name of partitions may have changed now,
         # allocatePartitions() takes care of this for new partitions, but not
@@ -1081,13 +1061,14 @@ class Request(object):
         self.device = device
         self.growth = 0                     # growth in sectors
         self.max_growth = 0                 # max growth in sectors
-        self.done = not device.req_grow     # can we grow this request more?
+        self.done = not getattr(device, "req_grow", True)  # can we grow this
+                                                           # request more?
         self.base = 0                       # base sectors
 
     @property
     def growable(self):
         """ True if this request is growable. """
-        return self.device.req_grow
+        return getattr(self.device, "req_grow", True)
 
     @property
     def id(self):
@@ -1188,6 +1169,8 @@ class Chunk(object):
             for req in requests:
                 self.addRequest(req)
 
+        self.skip_list = []
+
     def __repr__(self):
         s = ("%(type)s instance --\n"
              "device = %(device)s  length = %(length)d  size = %(size)d\n"
@@ -1212,6 +1195,22 @@ class Chunk(object):
         if not req.done:
             self.base += req.base
 
+    def reclaim(self, request, amount):
+        """ Reclaim units from a request and return them to the pool. """
+        log.debug("reclaim: %s %d (%d MB)" % (request, amount, self.lengthToSize(amount)))
+        if request.growth < amount:
+            log.error("tried to reclaim %d from request with %d of growth"
+                        % (amount, request.growth))
+            raise ValueError("cannot reclaim more than request has grown")
+
+        request.growth -= amount
+        self.pool += amount
+
+        # put this request in the skip list so we don't try to grow it the
+        # next time we call growRequests to allocate the newly re-acquired pool
+        if request not in self.skip_list:
+            self.skip_list.append(request)
+
     @property
     def growth(self):
         """ Sum of growth for all requests in this chunk. """
@@ -1291,7 +1290,7 @@ class Chunk(object):
             log.debug("%d requests and %d (%dMB) left in chunk" %
                         (self.remaining, self.pool, self.lengthToSize(self.pool)))
             for p in self.requests:
-                if p.done:
+                if p.done or p in self.skip_list:
                     continue
 
                 if not uniform:
@@ -1336,6 +1335,10 @@ class Chunk(object):
                 if self.pool == 0:
                     break
 
+        # requests that were skipped over this time through are back on the
+        # table next time
+        self.skip_list = []
+
 
 class DiskChunk(Chunk):
     """ A free region on disk from which partitions will be allocated """
@@ -1549,7 +1552,121 @@ def getDiskChunks(disk, partitions, free):
 
     return chunks
 
-def growPartitions(disks, partitions, free):
+class TotalSizeSet(object):
+    """ Set of device requests with a target combined size.
+
+        This will be handled by growing the requests until the desired combined
+        size has been achieved.
+    """
+    def __init__(self, devices, size):
+        self.devices = devices
+        self.size = size
+
+        self.requests = []
+
+        self.allocated = sum([d.req_base_size for d in self.devices])
+        log.debug("set.allocated = %d" % self.allocated)
+
+    def allocate(self, amount):
+        log.debug("allocating %d to TotalSizeSet with %d/%d (%d needed)"
+                    % (amount, self.allocated, self.size, self.needed))
+        self.allocated += amount
+
+    @property
+    def needed(self):
+        return self.size - self.allocated
+
+    def deallocate(self, amount):
+        log.debug("deallocating %d from TotalSizeSet with %d/%d (%d needed)"
+                    % (amount, self.allocated, self.size, self.needed))
+        self.allocated -= amount
+
+class SameSizeSet(object):
+    """ Set of device requests with a common target size. """
+    def __init__(self, devices, size, grow=False, max_size=None):
+        self.devices = devices
+        self.size = int(size / len(devices))
+        self.grow = grow
+        self.max_size = max_size
+
+        self.requests = []
+
+def manageSizeSets(size_sets, chunks):
+    growth_by_request = {}
+    requests_by_device = {}
+    chunks_by_request = {}
+    for chunk in chunks:
+        for request in chunk.requests:
+            requests_by_device[request.device] = request
+            chunks_by_request[request] = chunk
+            growth_by_request[request] = 0
+
+    for i in range(2):
+        reclaimed = dict([(chunk, 0) for chunk in chunks])
+        for ss in size_sets:
+            if isinstance(ss, TotalSizeSet):
+                # TotalSizeSet members are trimmed to achieve the requested
+                # total size
+                log.debug("set: %s %d/%d" % ([d.name for d in ss.devices],
+                                              ss.allocated, ss.size))
+
+                for device in ss.devices:
+                    request = requests_by_device[device]
+                    if request.done:
+                        continue
+
+                    chunk = chunks_by_request[request]
+                    new_growth = request.growth - growth_by_request[request]
+                    ss.allocate(chunk.lengthToSize(new_growth))
+
+                # decide how much to take back from each request
+                # We may assume that all requests have the same base size.
+                # We're shooting for a roughly equal distribution by trimming
+                # growth from the requests that have grown the most first.
+                requests = sorted([requests_by_device[d] for d in ss.devices],
+                                  key=lambda r: r.growth, reverse=True)
+                for request in requests:
+                    chunk = chunks_by_request[request]
+                    log.debug("%s" % request)
+                    log.debug("needed: %d" % ss.needed)
+
+                    if ss.needed < 0:
+                        # it would be good to take back some from each device
+                        # instead of taking all from the last one(s)
+                        extra = min(-chunk.sizeToLength(ss.needed),
+                                    request.growth)
+                        reclaimed[chunk] += extra
+                        chunk.reclaim(request, extra)
+                        ss.deallocate(chunk.lengthToSize(extra))
+
+                    if ss.needed <= 0:
+                        request.done = True
+
+            elif isinstance(ss, SameSizeSet):
+                # SameSizeSet members all have the same size as the smallest
+                # member
+                requests = [requests_by_device[d] for d in ss.devices]
+                min_growth = min([r.growth for r in requests])
+                for request in requests:
+                    if request.growth > min_growth:
+                        chunk = chunks_by_request[request]
+                        extra = request.growth - min_growth
+                        reclaimed[chunk] += extra
+                        chunk.reclaim(request, extra)
+                        request.done = True
+                    elif request.growth == min_growth:
+                        request.done = True
+
+        # store previous growth amounts so we know how much was allocated in
+        # the latest growRequests call
+        for request in growth_by_request.keys():
+            growth_by_request[request] = request.growth
+
+        for chunk in chunks:
+            if reclaimed[chunk] and not chunk.done:
+                chunk.growRequests()
+
+def growPartitions(disks, partitions, free, size_sets=None):
     """ Grow all growable partition requests.
 
         Partitions have already been allocated from chunks of free space on
@@ -1575,36 +1692,60 @@ def growPartitions(disks, partitions, free):
         log.debug("no growable partitions")
         return
 
+    if size_sets is None:
+        size_sets = []
+
     log.debug("growable partitions are %s" % [p.name for p in all_growable])
 
+    #
+    # collect info about each disk and the requests it contains
+    #
+    chunks = []
     for disk in disks:
-        log.debug("growing partitions on %s" % disk.name)
         sector_size = disk.format.partedDevice.sectorSize
 
-        # find any extended partition on this disk
-        extended_geometry = getattr(disk.format.extendedPartition,
-                                    "geometry",
-                                    None)  # parted.Geometry
-
         # list of free space regions on this disk prior to partition allocation
         disk_free = [f for f in free if f.device.path == disk.path]
         if not disk_free:
             log.debug("no free space on %s" % disk.name)
             continue
 
-        chunks = getDiskChunks(disk, partitions, disk_free)
-        log.debug("disk %s has %d chunks" % (disk.name, len(chunks)))
-        # grow the partitions in each chunk as a group
+        disk_chunks = getDiskChunks(disk, partitions, disk_free)
+        log.debug("disk %s has %d chunks" % (disk.name, len(disk_chunks)))
+        chunks.extend(disk_chunks)
+
+    #
+    # grow the partitions in each chunk as a group
+    #
+    for chunk in chunks:
+        if not chunk.hasGrowable:
+            # no growable partitions in this chunk
+            continue
+
+        chunk.growRequests()
+
+    # adjust set members' growth amounts as needed
+    manageSizeSets(size_sets, chunks)
+
+    for disk in disks:
+        log.debug("growing partitions on %s" % disk.name)
         for chunk in chunks:
+            if chunk.path != disk.path:
+                continue
+
             if not chunk.hasGrowable:
                 # no growable partitions in this chunk
                 continue
 
-            chunk.growRequests()
-
             # recalculate partition geometries
             disklabel = disk.format
             start = chunk.geometry.start
+
+            # find any extended partition on this disk
+            extended_geometry = getattr(disklabel.extendedPartition,
+                                        "geometry",
+                                        None)  # parted.Geometry
+
             # align start sector as needed
             if not disklabel.alignment.isAligned(chunk.geometry, start):
                 start = disklabel.alignment.alignUp(chunk.geometry, start)
@@ -1759,3 +1900,21 @@ def growLVM(storage):
             # initial size.
             req.device.size = chunk.lengthToSize(req.base + req.growth)
 
+def growBTRFS(storage):
+    """ Set up relative sizes for subvolumes after autopart for custom ui. """
+    for vol in storage.btrfsVolumes:
+        if vol.exists:
+            continue
+
+        requests = []
+        for subvol in vol.subvolumes:
+            request = Request(subvol)
+            request.base = subvol._size
+            requests.append(request)
+
+        chunk = Chunk(int(vol.size), requests=requests)
+        chunk.path = "btrfs %s" % vol.name
+        chunk.growRequests()
+
+        for req in chunk.requests:
+            req.device._size = chunk.lengthToSize(req.base + req.growth)
diff --git a/pyanaconda/storage/size.py b/pyanaconda/storage/size.py
index eb3e2e4..75ea933 100644
--- a/pyanaconda/storage/size.py
+++ b/pyanaconda/storage/size.py
@@ -139,27 +139,29 @@ class Size(Decimal):
 
         return self
 
-    def __str__(self):
+    def __str__(self, context=None):
         return self.humanReadable()
 
     def __repr__(self):
         return "Size('%s')" % self
 
-    def __add__(self, other):
-        return Size(bytes=Decimal.__add__(self, other))
+    def __add__(self, other, context=None):
+        return Size(bytes=Decimal.__add__(self, other, context=context))
 
     # needed to make sum() work with Size arguments
-    def __radd__(self, other):
-        return Size(bytes=Decimal.__radd__(self, other))
+    def __radd__(self, other, context=None):
+        return Size(bytes=Decimal.__radd__(self, other, context=context))
 
-    def __sub__(self, other):
-        return Size(bytes=Decimal.__sub__(self, other))
+    def __sub__(self, other, context=None):
+        # subtraction is implemented using __add__ and negation, so we'll
+        # be getting passed a Size
+        return Decimal.__sub__(self, other, context=context)
 
-    def __mul__(self, other):
-        return Size(bytes=Decimal.__mul__(self, other))
+    def __mul__(self, other, context=None):
+        return Size(bytes=Decimal.__mul__(self, other, context=context))
 
-    def __div__(self, other):
-        return Size(bytes=Decimal.__div__(self, other))
+    def __div__(self, other, context=None):
+        return Size(bytes=Decimal.__div__(self, other, context=context))
 
     def _trimEnd(self, val):
         """ Internal method to trim trailing zeros. """
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index 2793957..9964d21 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -32,13 +32,17 @@ _ = lambda x: gettext.ldgettext("anaconda", x)
 N_ = lambda x: x
 P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
 
+from contextlib import contextmanager
+
 from pykickstart.constants import *
 
 from pyanaconda.product import productName, productVersion
 from pyanaconda.storage.formats import device_formats
+from pyanaconda.storage.formats import getFormat
 from pyanaconda.storage.size import Size
 from pyanaconda.storage import Root
 from pyanaconda.storage.partitioning import doPartitioning
+from pyanaconda.storage.partitioning import doAutoPartition
 from pyanaconda.storage.errors import StorageError
 
 from pyanaconda.ui.gui import UIObject
@@ -51,10 +55,26 @@ from pyanaconda.ui.gui.categories.storage import StorageCategory
 
 from gi.repository import Gtk
 
+import logging
+log = logging.getLogger("anaconda")
+
 __all__ = ["CustomPartitioningSpoke"]
 
 new_install_name = _("New %s %s Installation") % (productName, productVersion)
 
+class UIStorageFilter(logging.Filter):
+    def filter(self, record):
+        record.name = "storage.ui"
+        return True
+
+@contextmanager
+def ui_storage_logger():
+    storage_log = logging.getLogger("storage")
+    f = UIStorageFilter()
+    storage_log.addFilter(f)
+    yield
+    storage_log.removeFilter(f)
+
 class AddDialog(UIObject):
     builderObjects = ["addDialog"]
     mainWidgetName = "addDialog"
@@ -125,8 +145,94 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         self._current_selector = None
         self._when_create_text = ""
+        self._devices = []
+        self._unused_devices = None     # None indicates uninitialized
+        self._free_space = Size(bytes=0)
+
+    def _propagate_actions(self, actions):
+        """ Register actions from the UI with the main Storage instance. """
+        for ui_action in actions:
+            ui_device = ui_action.device
+            device = self.storage.devicetree.getDeviceByID(ui_device.id)
+            if device is None:
+                # This device does not exist in the main devicetree.
+                #
+                # That means it was created in the ui. If it is still present in
+                # the ui device list, schedule a create action for it. If not,
+                # just ignore it.
+                if not ui_action.isCreate or ui_device not in self._devices:
+                    # this is a device that was created and then destroyed here
+                    continue
+
+            args = [device]
+            if ui_action.isResize:
+                args.append(ui_device.targetSize)
+
+            if ui_action.isCreate and ui_action.isFormat:
+                args.append(ui_device.format)
+
+            if ui_action.isCreate and ui_action.isDevice:
+                # We're going to just move the already-defined device into the
+                # main devicetree, but first we need to replace its parents
+                # with the corresponding devices from the main devicetree
+                # instead of the ones from our local devicetree.
+                parents = [self.storage.devicetree.getDeviceByID(p.id)
+                            for p in ui_device.parents]
+
+                if hasattr(ui_device, "partedPartition"):
+                    # This cleans up any references to parted.Disk instances
+                    # that only exist in our local devicetree.
+                    ui_device.partedPartition = None
+
+                # If we somehow ended up with a different number of parents
+                # go ahead and take a dump on the floor.
+                assert(len(parents) == len(ui_device.parents))
+                ui_device.parents = parents
+                args = [ui_device]
+
+            log.debug("duplicating action '%s'" % ui_action)
+            action = ui_action.__class__(*args)
+            self.storage.devicetree.registerAction(action)
 
     def apply(self):
+        ui_devicetree = self.__storage.devicetree
+
+        log.debug("converting custom spoke changes into actions")
+        for action in ui_devicetree.findActions():
+            log.debug("%s" % action)
+
+        # schedule actions for device removals, resizes
+        actions = ui_devicetree.findActions(type="destroy")
+        partition_destroys = [a for a in actions
+                                if a.device.type == "partition"]
+        actions.extend(ui_devicetree.findActions(type="resize"))
+        self._propagate_actions(actions)
+
+        # register all disklabel create actions
+        actions = ui_devicetree.findActions(type="create", object="format")
+        disklabel_creates = [a for a in actions
+                                if a.device.format.type == "disklabel"]
+        self._propagate_actions(disklabel_creates)
+
+        # register partition create actions, including formatting
+        actions = ui_devicetree.findActions(type="create")
+        partition_creates = [a for a in actions if a.device.type == "partition"]
+        self._propagate_actions(partition_creates)
+
+        if partition_creates or partition_destroys:
+            try:
+                doPartitioning(self.storage)
+            except Exception as e:
+                # TODO: error handling
+                raise
+
+        # register all other create actions
+        already_handled = disklabel_creates + partition_creates
+        actions = [a for a in ui_devicetree.findActions(type="create")
+                        if a not in already_handled]
+        self._propagate_actions(actions)
+
+        # set up bootloader and check the configuration
         self.storage.setUpBootLoader()
         StorageChecker.run(self)
 
@@ -147,7 +253,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._configButton = self.builder.get_object("configureButton")
 
     def initialize(self):
-        from pyanaconda.storage.devices import DiskDevice
         from pyanaconda.storage.formats.fs import FS
 
         NormalSpoke.initialize(self)
@@ -198,40 +303,40 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         else:
             return None
 
+    @property
     def _clearpartDevices(self):
-        return [d for d in self.storage.devicetree.devices if d.name in self.data.clearpart.drives]
-
-    def _unusedDevices(self):
-        from pyanaconda.storage.devices import DiskDevice
-        return [d for d in self.storage.unusedDevices if d.disks and not isinstance(d, DiskDevice)]
+        return [d for d in self._devices if d.name in self.data.clearpart.drives]
 
-    def _currentFreeSpace(self):
-        """Add up all the free space on selected disks and return it as a Size."""
-        totalFree = Size(bytes=0)
+    @property
+    def unusedDevices(self):
+        if self._unused_devices is None:
+            self._unused_devices = [d for d in self.__storage.unusedDevices
+                                        if d.disks and not d.isDisk]
 
-        freeDisks = self.storage.getFreeSpace(disks=self._clearpartDevices())
-        for tup in freeDisks.values():
-            for chunk in tup:
-                totalFree += chunk
+        return self._unused_devices
 
-        return totalFree
+    def _setCurrentFreeSpace(self):
+        """Add up all the free space on selected disks and return it as a Size."""
+        freeDisks = self.__storage.getFreeSpace(clearPartType=CLEARPART_TYPE_NONE)
+        self._free_space = sum([f[0] for f in freeDisks.values()])
 
     def _currentTotalSpace(self):
         """Add up the sizes of all selected disks and return it as a Size."""
         totalSpace = 0
 
-        for disk in self._clearpartDevices():
+        for disk in self._clearpartDevices:
             totalSpace += disk.size
 
         return Size(spec="%s MB" % totalSpace)
 
     def _updateSpaceDisplay(self):
         # Set up the free space/available space displays in the bottom left.
+        self._setCurrentFreeSpace()
         self._availableSpaceLabel = self.builder.get_object("availableSpaceLabel")
         self._totalSpaceLabel = self.builder.get_object("totalSpaceLabel")
         self._summaryButton = self.builder.get_object("summary_button")
 
-        self._availableSpaceLabel.set_text(str(self._currentFreeSpace()))
+        self._availableSpaceLabel.set_text(str(self._free_space))
         self._totalSpaceLabel.set_text(str(self._currentTotalSpace()))
 
         summaryLabel = self._summaryButton.get_children()[0]
@@ -245,7 +350,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
     def refresh(self):
         NormalSpoke.refresh(self)
+        self.__storage = self.storage.copy()
+        self.__storage.devicetree._actions = [] # keep things simple
+        self._devices = self.__storage.devices
         self._do_refresh()
+
+        # update our free space number based on Storage
+        self._setCurrentFreeSpace()
+
         self._updateSpaceDisplay()
 
     def _do_refresh(self):
@@ -261,9 +373,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # We can only have one page expanded at a time.
         did_expand = False
 
-        unused = self._unusedDevices()
-        new_devices = [d for d in self.storage.devicetree.leaves if d not in unused and not d.exists]
-        roots = self.storage.roots[:]
+        unused = [d for d in self.unusedDevices if d.isleaf]
+        new_devices = [d for d in self._devices if not d.exists]
+
+        log.debug("ui: unused=%s" % [d.name for d in unused])
+        log.debug("ui: new_devices=%s" % [d.name for d in new_devices])
+
+        ui_roots = self.__storage.roots[:]
 
         # If we've not yet run autopart, add an instance of CreateNewPage.  This
         # ensures it's only added once.
@@ -278,39 +394,44 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             label = self.builder.get_object("whenCreateLabel")
             label.set_text(self._when_create_text % (productName, productVersion))
         else:
-            swaps = [d for d in new_devices if d in self.storage.swaps]
-            mounts = dict([(d.format.mountpoint, d) for d in new_devices if getattr(d.format, "mountpoint", None)])
+            swaps = [d for d in new_devices if d.format.type == "swap"]
+            mounts = dict([(d.format.mountpoint, d) for d in new_devices
+                                if getattr(d.format, "mountpoint", None)])
             new_root = Root(mounts=mounts, swaps=swaps, name=new_install_name)
-            roots.insert(0, new_root)
+            ui_roots.insert(0, new_root)
 
         # Add in all the existing (or autopart-created) operating systems.
-        for root in roots:
-            if root.device and root.device not in self.storage.devices:
+        for root in ui_roots:
+            # don't make a page if none of the root's devices are left
+            if not [d for d in root.swaps + root.mounts.values()
+                        if d in self._devices]:
                 continue
 
             page = Page()
             page.pageTitle = root.name
 
             for device in root.swaps:
-                if device not in self.storage.devices:
+                if device not in self._devices:
                     continue
 
-                selector = page.addDevice("Swap", device.size, None, self.on_selector_clicked)
+                selector = page.addDevice("Swap",
+                                          Size(spec="%f MB" % device.size),
+                                          None, self.on_selector_clicked)
                 selector._device = device
                 selector._root = root
 
             for (mountpoint, device) in root.mounts.iteritems():
-                if device not in self.storage.devices:
+                if device not in self._devices:
                     continue
 
-                selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, device.size, mountpoint, self.on_selector_clicked)
+                selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, Size(spec="%f MB" % device.size), mountpoint, self.on_selector_clicked)
                 selector._device = device
                 selector._root = root
 
             page.show_all()
             self._accordion.addPage(page, cb=self.on_page_clicked)
 
-            if not did_expand and getattr(self._current_selector, "_root", None) and root.name == self._current_selector._root.name:
+            if not did_expand:
                 did_expand = True
                 self._accordion.expandPage(root.name)
 
@@ -320,14 +441,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             page.pageTitle = _("Unknown")
 
             for u in unused:
-                selector = page.addDevice(u.format.name, u.size, None, self.on_selector_clicked)
+                selector = page.addDevice(u.format.name, Size(spec="%f MB" % u.size), None, self.on_selector_clicked)
                 selector._device = u
                 selector._root = None
 
             page.show_all()
             self._accordion.addPage(page, cb=self.on_page_clicked)
 
-            if not did_expand and self._current_selector and unused == self._current_selector._root:
+            if not did_expand:
                 did_expand = True
                 self._accordion.expandPage(page.pageTitle)
 
@@ -361,7 +482,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         device = selector._device
 
-        if hasattr(device.format, "label") and labelEntry.get_text():
+        if labelEntry.get_text() and hasattr(device.format, "label"):
             device.format.label = labelEntry.get_text()
 
     def _populate_right_side(self, selector):
@@ -379,14 +500,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         selectedDeviceDescLabel.set_text(self._description(selector.props.name))
 
         labelEntry.set_text(getattr(device.format, "label", "") or "")
-        labelEntry.set_sensitive(getattr(device.format, "labelfsProg", "") != "")
+        can_label = getattr(device.format, "labelfsProg", "") != ""
+        labelEntry.set_sensitive(can_label)
 
         if labelEntry.get_sensitive():
             labelEntry.props.has_tooltip = False
         else:
             labelEntry.set_tooltip_text(_("This file system does not support labels."))
 
-        sizeSpinner.set_range(device.minSize, device.maxSize)
+        sizeSpinner.set_range(device.minSize,
+                              device.maxSize)
         sizeSpinner.set_value(device.size)
         sizeSpinner.set_sensitive(device.resizable)
 
@@ -400,15 +523,17 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # FIXME:  What do we do if we can't figure it out?
         if device.type == "lvmlv":
             typeCombo.set_active(1)
-        elif device.type in ["dm-raid array", "mdarray"]:
+        elif device.type == "mdarray":
             typeCombo.set_active(2)
         elif device.type == "partition":
             typeCombo.set_active(3)
+        elif device.type.startswith("btrfs"):
+            typeCombo.set_active(0)
 
         # FIXME:  What do we do if we can't figure it out?
         model = fsCombo.get_model()
         for i in range(0, len(model)):
-            if model[i][0] == device.format.name:
+            if model[i][0] == device.format.type:
                 fsCombo.set_active(i)
                 break
 
@@ -439,47 +564,68 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # create a device of the default type, using any disks, with an
         # appropriate fstype and mountpoint
         mountpoint = dialog.mountpoint
-        size = dialog.size
-        fstype = self.storage.defaultFSType
+        log.debug("requested size = %s  ; available space = %s"
+                    % (dialog.size, self._free_space))
+        size = min(dialog.size, self._free_space)
+        fstype = self.storage.getFSType(mountpoint)
+        encrypted = self.data.autopart.encrypted
+
+        # we're doing nothing here to ensure that bootable requests end up on
+        # the boot disk, but the weight from platform should take care of this
+
         if mountpoint.lower() == "swap":
-            fstype = "swap"
             mountpoint = None
-        elif mountpoint == "/boot":
-            fstype = self.storage.defaultBootFSType
-        elif mountpoint == "/boot/efi":
-            if iutil.isMactel():
-                fstype = "hfs+"
-            else:
-                fstype = "efi"
-        elif not mountpoint or not mountpoint.startswith("/") or \
-             mountpoint in self.storage.mountpoints.keys():
-            return
 
-        if not size:
-            # no size specified, so use the default size
-            size = None
-
-        if self.data.autopart.type == AUTOPART_TYPE_PLAIN:
-            # create a partition for this new filesystem
-            size_mb = float(size.convertTo(spec="mb"))
-            device = self.storage.newPartition(size=size_mb,
-                                               fmt_type=fstype,
-                                               mountpoint=mountpoint)
-            self.storage.createDevice(device)
-            try:
-                doPartitioning(self.storage)
-            except StorageError as e:
-                actions = self.storage.devicetree.findActions(device=device)
-                for a in reversed(actions):
-                    self.storage.devicetree.cancelAction(a)
-            else:
-                self._do_refresh()
+        # TODO: validate the mountpoint for sanity and uniqueness
+
+        device_type = self.data.autopart.type
+        if device_type == AUTOPART_TYPE_LVM and \
+             mountpoint == "/boot/efi":
+            device_type = AUTOPART_TYPE_PLAIN
+        elif device_type == AUTOPART_TYPE_BTRFS and \
+             (fstype == "swap" or \
+              (mountpoint and mountpoint.startswith("/boot"))):
+            device_type = AUTOPART_TYPE_PLAIN
+
+        disks = self._clearpartDevices
+
+        with ui_storage_logger():
+            self.__storage.newDevice(device_type,
+                                     size=float(size.convertTo(spec="mb")),
+                                     fstype=fstype,
+                                     mountpoint=mountpoint,
+                                     encrypted=encrypted,
+                                     disks=disks)
+        self._devices = self.__storage.devices
+        self._do_refresh()
+        self._updateSpaceDisplay()
 
     def _destroy_device(self, device):
+        with ui_storage_logger():
+            self.__storage.destroyDevice(device)
+
+        self._devices = self.__storage.devices
+
+        # should this be in DeviceTree._removeDevice?
+        container = None
+        if hasattr(device, "vg"):
+            device.vg._removeLogVol(device)
+            container = device.vg
+            device_type = AUTOPART_TYPE_LVM
+        elif hasattr(device, "volume"):
+            device.volume._removeSubVolume(device.name)
+            container = device.volume
+            device_type = AUTOPART_TYPE_BTRFS
+
+        if container and not container.exists:
+            # adjust container to size of remaining devices
+            with ui_storage_logger():
+                # TODO: raid
+                factory = self.__storage.getDeviceFactory(device_type, 0)
+                parents = self.__storage.setContainerMembers(container, factory)
+
         # if this device has parents with no other children, remove them too
-        parents = device.parents[:]
-        self.storage.destroyDevice(device)
-        for parent in parents:
+        for parent in device.parents:
             if parent.kids == 0 and not parent.isDisk:
                 self._destroy_device(parent)
 
@@ -488,8 +634,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             pass    # unused device
         elif device in root.swaps:
             root.swaps.remove(device)
-        elif hasattr(device.format, "mountpoint") and \
-             device in root.mounts.values():
+        elif device in root.mounts.values():
             mountpoints = [m for (m,d) in root.mounts.items() if d == device]
             for mountpoint in mountpoints:
                 root.mounts.pop(mountpoint)
@@ -520,7 +665,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
                 # schedule actions to delete the thing.
                 dialog = ConfirmDeleteDialog(self.data)
                 with enlightbox(self.window, dialog.window):
-                    dialog.refresh(getattr(device.format, "mountpoint", None), device.name)
+                    dialog.refresh(getattr(device.format, "mountpoint", ""),
+                                   device.name)
                     rc = dialog.run()
 
                     if rc == 0:
@@ -530,12 +676,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
             self._remove_from_root(root, device)
             self._destroy_device(device)
-
-            # If the root is now empty, remove it. Devices from the Unused page
-            # will have no root.
-            if root and root in self.storage.roots and len(root.swaps + root.mounts.values()) == 0:
-                self.storage.roots.remove(root)
-
             self._update_ui_for_removals()
         elif self._accordion.currentPage():
             # This is a complete installed system.  Thus, we first need to confirm
@@ -562,19 +702,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             for device in root.swaps + root.mounts.values():
                 self._destroy_device(device)
 
-            # Remove the root entirely.  This will cause the empty Page to be
-            # deleted from the left hand side.
-            if root in self.storage.roots:
-                self.storage.roots.remove(root)
-
             self._update_ui_for_removals()
 
     def on_summary_clicked(self, button):
-        free = self.storage.getFreeSpace()
+        free = self._free_space
         dialog = SelectedDisksDialog(self.data)
 
         with enlightbox(self.window, dialog.window):
-            dialog.refresh(self._clearpartDevices(), free, showRemove=False)
+            dialog.refresh(self._clearpartDevices, free, showRemove=False)
             dialog.run()
 
     def on_configure_clicked(self, button):
@@ -608,20 +743,28 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         self._removeButton.set_sensitive(True)
 
-    def on_create_clicked(self, button):
-        from pyanaconda.storage import Root
-        from pyanaconda.storage.devices import DiskDevice
-        from pyanaconda.storage.partitioning import doAutoPartition
+    def _do_autopart(self):
+        # There are never any non-existent devices around when this runs.
+        log.debug("running automatic partitioning")
+        self.__storage.doAutoPart = True
+        with ui_storage_logger():
+            doAutoPartition(self.__storage, self.data)
+        self.__storage.doAutoPart = False
+        self._devices = self.__storage.devices
+        log.debug("finished automatic partitioning")
 
+    def on_create_clicked(self, button):
         # Then do autopartitioning.  We do not do any clearpart first.  This is
         # custom partitioning, so you have to make your own room.
-        # FIXME:  Handle all the autopart exns here.
-        self.data.autopart.autopart = True
-        self.data.autopart.execute(self.storage, self.data, self.instclass)
-        self.data.autopart.autopart = False
+        self._do_autopart()
 
         # Refresh the spoke to make the new partitions appear.
-        self.refresh()
+        log.debug("refreshing ui")
+        self._do_refresh()
+        log.debug("finished refreshing ui")
+        log.debug("updating space display")
+        self._updateSpaceDisplay()
+        log.debug("finished updating space display")
         self._accordion.expandPage(new_install_name)
 
         # And then display the first filesystem on the RHS.
@@ -641,3 +784,5 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             self._optionsNotebook.set_current_page(2)
         elif text == _("Standard Partition"):
             self._optionsNotebook.hide()
+
+
diff --git a/pyanaconda/ui/gui/spokes/lib/accordion.py b/pyanaconda/ui/gui/spokes/lib/accordion.py
index c2da71e..09b87e9 100644
--- a/pyanaconda/ui/gui/spokes/lib/accordion.py
+++ b/pyanaconda/ui/gui/spokes/lib/accordion.py
@@ -23,7 +23,6 @@ import gettext
 _ = lambda x: gettext.ldgettext("anaconda", x)
 
 from pyanaconda.product import productName, productVersion
-from pyanaconda.storage.size import Size
 
 from gi.repository.AnacondaWidgets import MountpointSelector
 from gi.repository import Gtk
@@ -137,7 +136,7 @@ class Page(Gtk.Box):
 
     def addDevice(self, name, size, mountpoint, cb):
         selector = MountpointSelector()
-        selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "")
+        selector = MountpointSelector(name, str(size).upper(), mountpoint or "")
         selector.connect("button-press-event", self._onSelectorClicked, cb)
         selector.connect("key-release-event", self._onSelectorClicked, cb)
         selector.connect("focus-in-event", self._onSelectorClicked, cb)
@@ -182,7 +181,7 @@ class UnknownPage(Page):
 
     def addDevice(self, name, size, mountpoint, cb):
         selector = MountpointSelector()
-        selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "")
+        selector = MountpointSelector(name, str(size).upper(), mountpoint or "")
         selector.connect("button-press-event", self._onSelectorClicked, cb)
         selector.connect("key-release-event", self._onSelectorClicked, cb)
         selector.connect("focus-in-event", self._onSelectorClicked, cb)
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
index 6e5df44..0081705 100644
--- a/pyanaconda/ui/gui/spokes/storage.py
+++ b/pyanaconda/ui/gui/spokes/storage.py
@@ -312,6 +312,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
         self.data.clearpart.initAll = True
         self.data.clearpart.type = self.clearPartType
         self.storage.config.update(self.data)
+        self.storage.autoPartType = self.data.autopart.type
 
         # If autopart is selected we want to remove whatever has been
         # created/scheduled to make room for autopart.
@@ -515,8 +516,23 @@ class StorageSpoke(NormalSpoke, StorageChecker):
                                                clearPartType=CLEARPART_TYPE_ALL)
         disk_free = sum([f[0] for f in free_space.itervalues()])
         fs_free = sum([f[1] for f in free_space.itervalues()])
+
+        # add in total size of new filesystems
+        new_free_mb = 0
+        for mountpoint, device in self.storage.mountpoints.items():
+            if mountpoint != "/" and not mountpoint.startswith("/usr") and \
+               not mountpoint.startswith("/var"):
+                # only count system mounts
+                continue
+
+            if device.format.exists:
+                new_free_mb += getattr(device.format, "free", 0)
+            else:
+                new_free_mb += device.size
+        new_free = Size(spec="%f MB" % new_free_mb)
+
         required_space = self.payload.spaceRequired
-        if disk_free >= required_space:
+        if disk_free + new_free >= required_space:
             dialog = InstallOptions1Dialog(self.data)
         elif sum([d.size for d in disks]) >= required_space:
             dialog = InstallOptions2Dialog(self.data)
