diff --git a/data/70-anaconda.rules b/data/70-anaconda.rules
index 139c1f5..40f4459 100644
--- a/data/70-anaconda.rules
+++ b/data/70-anaconda.rules
@@ -23,5 +23,7 @@ ENV{ID_FS_TYPE}=="isw_raid_member", IMPORT{program}="$env{ANACBIN}/mdadm --exami
 ENV{ID_FS_TYPE}=="LVM2_member", IMPORT{program}="$env{ANACBIN}/lvm pvs --config 'global {locking_type=4}' --units k --nosuffix --nameprefixes --rows --unquoted --noheadings -opv_name,pv_uuid,pv_size,pv_pe_count,pv_pe_alloc_count,pe_start,vg_name,vg_uuid,vg_size,vg_free,vg_extent_size,vg_extent_count,vg_free_count,pv_count $tempnode"
 ENV{ID_FS_TYPE}=="LVM2_member", IMPORT{program}="$env{ANACBIN}/lvm pvs --config 'global {locking_type=4}' --units k --nosuffix --nameprefixes --rows --unquoted --noheadings -olv_name,lv_uuid,lv_size,lv_attr $tempnode"
 
+ENV{ID_FS_TYPE}=="btrfs", PROGRAM="btrfs scan $tempnode"
+
 LABEL="anaconda_end"
 
diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py
index 0d784f2..87cf0da 100644
--- a/pyanaconda/installclass.py
+++ b/pyanaconda/installclass.py
@@ -87,19 +87,19 @@ class BaseInstallClass(object):
 		 "sshd",
 		 "language",
 		 "keyboard",
-                 "filtertype",
-                 "filter",
+#                 "filtertype",
+#                 "filter",
                  "storageinit",
-                 "findrootparts",
-		 "betanag",
+#                 "findrootparts",
+#		 "betanag",
                  "cleardiskssel",
                  "parttype",
                  "autopartitionexecute",
 		 "storagedone",
 		 "bootloader",
 		 "network",
-		 "timezone",
-		 "accounts",
+#		 "timezone",
+#		 "accounts",
                  "reposetup",
                  "basepkgsel",
 		 "tasksel",
@@ -175,10 +175,10 @@ class BaseInstallClass(object):
     def setDefaultPartitioning(self, storage, platform):
         autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType,
                                  size=1024, maxSize=50*1024, grow=True,
-                                 asVol=True, encrypted=True),
+                                 btr=True, lv=True, encrypted=True),
                         PartSpec(mountpoint="/home", fstype=storage.defaultFSType,
                                  size=500, grow=True, requiredSpace=50*1024,
-                                 asVol=True, encrypted=True)]
+                                 btr=True, lv=True, encrypted=True)]
 
         bootreq = platform.setDefaultPartitioning()
         if bootreq:
@@ -186,7 +186,7 @@ class BaseInstallClass(object):
 
         (minswap, maxswap) = iutil.swapSuggestion()
         autorequests.append(PartSpec(fstype="swap", size=minswap, maxSize=maxswap,
-                                     grow=True, asVol=True, encrypted=True))
+                                     grow=True, lv=True, encrypted=True))
 
         storage.autoPartitionRequests = autorequests
 
diff --git a/pyanaconda/iw/partition_gui.py b/pyanaconda/iw/partition_gui.py
index c62d0a6..df2a751 100644
--- a/pyanaconda/iw/partition_gui.py
+++ b/pyanaconda/iw/partition_gui.py
@@ -50,7 +50,9 @@ from pyanaconda.constants import *
 from partition_ui_helpers_gui import *
 from pyanaconda.storage.partitioning import doPartitioning
 from pyanaconda.storage.devicelibs import lvm
-from pyanaconda.storage.devices import devicePathToName, PartitionDevice
+from pyanaconda.storage.devices import devicePathToName
+from pyanaconda.storage.devices import PartitionDevice
+from pyanaconda.storage.devices import BTRFSDevice
 from pyanaconda.storage.devices import deviceNameToDiskByPath
 from pyanaconda.storage.errors import DeviceNotFoundError
 
@@ -492,27 +494,32 @@ class LVMStripeGraph(StripeGraph):
 
         return stripe
 
-class MDRaidArrayStripeGraph(StripeGraph):
+class MDStripeGraph(StripeGraph):
+    desc = "MD"
     """
     storage -- the storage object
 
     cCB -- call back function used when the user clicks on a slice. This function
            is passed a device object when its executed.
     dcCB -- call back function used when the user double clicks on a slice.
-    md -- RAID device to display.
+    md -- md device to display.
     """
-    def __init__(self, storage, md=None, cCB=lambda x:None, dcCB=lambda:None):
+    def __init__(self, storage, device=None, cCB=lambda x:None, dcCB=lambda:None):
         StripeGraph.__init__(self)
         self.storage = storage
         self.cCB = cCB
         self.dcCB = dcCB
         self.part_type_colors = \
                 {"sel_md": "cornsilk1", "unsel_md": "white"}
-        if md:
-            self.setDisplayed(md)
+        if device:
+            self.setDisplayed(device)
+
+    def _get_text(self, md):
+        return (_("%(desc)s %(mdPath)s (%(mdSize)-0.f MB)")
+                 % {"mdPath": md.path, "mdSize": md.size, "desc": self.desc})
 
     def _createStripe(self, md):
-        mdtext = _("MD RAID ARRAY %(mdPath)s (%(mdSize)-0.f MB)") % {"mdPath": md.path, "mdSize": md.size}
+        mdtext = self._get_text(md)
         stripe = Stripe(self.getCanvas(), mdtext, self.dcCB, obj = md)
 
         # Since we can't really create subslices with md devices we will only
@@ -531,6 +538,16 @@ class MDRaidArrayStripeGraph(StripeGraph):
 
         return stripe
 
+class MDRaidArrayStripeGraph(MDStripeGraph):
+    desc = "MD RAID Array"
+
+class BTRFSStripeGraph(MDStripeGraph):
+    desc = "BTRFS Pool"
+
+    def _get_text(self, md):
+        return (_("%(desc)s %(mdUUID)s (%(mdSize)-0.f MB)")
+                 % {"mdUUID": md.uuid, "mdSize": md.size, "desc": self.desc})
+
 class MessageGraph:
     def __init__(self, canvas, message):
         self.canvas = canvas
@@ -925,14 +942,36 @@ class PartitionWindow(InstallWindow):
                     break
 
             mnt_str = getattr(array, "name", "")
+        elif format.type == "btrfs" and not isinstance(device, BTRFSDevice):
+            btrfs_dev = self.storage.devicetree.getChildren(device)[0]
+            mnt_str = btrfs_dev.name
         else:
             mnt_str = getattr(format, "mountpoint", "")
             if mnt_str is None:
                 mnt_str = ""
 
+        isleaf = True
+
         # device name
         name_str = getattr(device, "lvname", device.name)
 
+        # XXX can this move up one level?
+        if isinstance(device, BTRFSDevice):
+            # list subvolumes as children of the main volume
+            for s in device.subvolumes:
+                isleaf = False
+                if s.format.exists:
+                    sub_format_icon = None
+                else:
+                    sub_format_icon = self.checkmark_pixbuf
+                subvol_iter = self.tree.append(treeiter)
+                self.tree[subvol_iter]['Device'] = s.name
+                self.tree[subvol_iter]['PyObject'] = s
+                self.tree[subvol_iter]['IsFormattable'] = False
+                self.tree[subvol_iter]['Format'] = sub_format_icon
+                self.tree[subvol_iter]['Mount Point'] = s.format.mountpoint
+                self.tree[subvol_iter]['Type'] = "btrfs subvolume"
+
         # label
         label_str = getattr(format, "label", "")
         if label_str is None:
@@ -944,7 +983,7 @@ class PartitionWindow(InstallWindow):
         self.tree[treeiter]['IsFormattable'] = format.formattable
         self.tree[treeiter]['Format'] = format_icon
         self.tree[treeiter]['Mount Point'] = mnt_str
-        self.tree[treeiter]['IsLeaf'] = True
+        self.tree[treeiter]['IsLeaf'] = isleaf
         self.tree[treeiter]['Type'] = format.name
         self.tree[treeiter]['Label'] = label_str
 
@@ -985,6 +1024,15 @@ class PartitionWindow(InstallWindow):
                             (array.name, array.path)
                 self.tree[iter]['Device'] = name
 
+        # BTRFS volumes
+        btrfs_devs = self.storage.btrfsVolumes
+        if btrfs_devs:
+            btrfsparent = self.tree.append(None)
+            self.tree[btrfsparent]['Device'] = _("BTRFS Volumes")
+            for dev in btrfs_devs:
+                iter = self.tree.append(btrfsparent)
+                self.addDevice(dev, iter)
+
         # now normal partitions
         disks = self.storage.partitioned
         # also include unpartitioned disks that aren't mpath or biosraid
@@ -1262,13 +1310,26 @@ class PartitionWindow(InstallWindow):
             if not isinstance(self.stripeGraph, MDRaidArrayStripeGraph):
                 self.stripeGraph.shutDown()
                 self.stripeGraph = MDRaidArrayStripeGraph(self.storage,
-                        md = device,
+                        device = device,
                         cCB = self.tree.selectRowFromObj,
                         dcCB = self.barviewActivateCB)
             self.stripeGraph.setDisplayed(device)
             self.deleteButton.set_sensitive(True)
             self.editButton.set_sensitive(True)
 
+        elif isinstance(device, storage.BTRFSDevice):
+            # BTRFSDevice can be edited but not explicitly deleted. It is
+            # deleted when its last member device is removed.
+            if not isinstance(self.stripeGraph, BTRFSStripeGraph):
+                self.stripeGraph.shutDown()
+                self.stripeGraph = BTRFSStripeGraph(self.storage,
+                        device = device,
+                        cCB = self.tree.selectRowFromObj,
+                        dcCB = self.barviewActivateCB)
+            self.stripeGraph.setDisplayed(device)
+            self.deleteButton.set_sensitive(False)
+            self.editButton.set_sensitive(True)
+
         else:
             # This means that the user selected something that is not showable
             # in the bar view.  Just show the information message.
diff --git a/pyanaconda/iw/partition_ui_helpers_gui.py b/pyanaconda/iw/partition_ui_helpers_gui.py
index 5a63d2c..eb7d08d 100644
--- a/pyanaconda/iw/partition_ui_helpers_gui.py
+++ b/pyanaconda/iw/partition_ui_helpers_gui.py
@@ -107,6 +107,7 @@ def createMountPointCombo(request, excludeMountPoints=[]):
         else:
             mountCombo.get_children()[0].set_text("")
     else:
+        # XXX special case for btrfs? (show the mountpoint but disallow edit)
         mountCombo.get_children()[0].set_text(_("<Not Applicable>"))
         mountCombo.set_sensitive(0)
 
@@ -129,6 +130,7 @@ def setMntPtComboStateFromType(fmt_class, mountCombo):
         else:
             mountCombo.get_children()[0].set_text("")
     else:
+        # XXX special case for btrfs? (show the mountpoint but disallow edit)
         if mountCombo.get_children()[0].get_text() != _("<Not Applicable>"):
             mountCombo.set_data("saved_mntpt", mountCombo.get_children()[0].get_text())
         mountCombo.get_children()[0].set_text(_("<Not Applicable>"))
diff --git a/pyanaconda/platform.py b/pyanaconda/platform.py
index fa0df8f..b8fd440 100644
--- a/pyanaconda/platform.py
+++ b/pyanaconda/platform.py
@@ -288,7 +288,7 @@ class S390(Platform):
         from storage.partspec import PartSpec
         return [PartSpec(mountpoint="/boot", size=500,
                          fstype=self.anaconda.storage.defaultBootFSType,
-                         weight=self.weight(mountpoint="/boot"), asVol=True,
+                         weight=self.weight(mountpoint="/boot"), lv=True,
                          singlePV=True)]
 
     def requiredDiskLabelType(self, device_type):
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py
index 47c93fe..98a6d46 100644
--- a/pyanaconda/storage/__init__.py
+++ b/pyanaconda/storage/__init__.py
@@ -44,7 +44,6 @@ from deviceaction import *
 from formats import getFormat
 from formats import get_device_format_class
 from formats import get_default_filesystem_type
-from devicelibs.lvm import safeLvmName
 from devicelibs.dm import name_from_dm_node
 from devicelibs.crypto import generateBackupPassphrase
 from devicelibs.mpath import MultipathConfigWriter
@@ -350,7 +349,8 @@ class Storage(object):
         self.doAutoPart = False
         self.clearPartChoice = None
         self.encryptedAutoPart = False
-        self.lvmAutoPart = True
+        self.lvmAutoPart = False
+        self.btrfsAutoPart = True
         self.encryptionPassphrase = None
         self.escrowCertificates = {}
         self.autoPartEscrowCert = None
@@ -707,6 +707,10 @@ class Storage(object):
             _platform = getattr(self.anaconda, "platform", None)
         return _platform
 
+    @property
+    def names(self):
+        return self.devicetree.names
+
     def exceptionDisks(self):
         """ Return a list of removable devices to save exceptions to.
 
@@ -872,9 +876,9 @@ class Storage(object):
             hostname = ""
             if hasattr(self.anaconda, "network"):
                 hostname = self.anaconda.network.hostname
-            name = self.createSuggestedVGName(hostname=hostname)
+            name = self.suggestContainerName(hostname=hostname, prefix="vg")
 
-        if name in [d.name for d in self.devices]:
+        if name in self.names:
             raise ValueError("name already in use")
 
         return LVMVolumeGroupDevice(name, pvs, *args, **kwargs)
@@ -896,15 +900,68 @@ class Storage(object):
                 swap = True
             else:
                 swap = False
-            name = self.createSuggestedLVName(vg,
-                                              swap=swap,
-                                              mountpoint=mountpoint)
+            name = self.suggestDeviceName(prefix="lv",
+                                          parent=vg,
+                                          swap=swap,
+                                          mountpoint=mountpoint)
 
-        if name in [d.name for d in self.devices]:
+        if name in self.names:
             raise ValueError("name already in use")
 
         return LVMLogicalVolumeDevice(name, vg, *args, **kwargs)
 
+    def newBTRFS(self, *args, **kwargs):
+        """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice. """
+        log.debug("newBTRFS: args = %s ; kwargs = %s" % (args, kwargs))
+        name = kwargs.pop("name", None)
+        if args:
+            name = args[0]
+
+        mountpoint = kwargs.pop("mountpoint", None)
+        fmt_kwargs = {"mountpoint": mountpoint}
+
+        if kwargs.get("subvol", False):
+            kwargs.pop("subvol")
+            dev_class = BTRFSSubVolumeDevice
+            # make sure there's a valid parent device
+            parents = kwargs.get("parents", [])
+            if not parents or len(parents) != 1 or \
+               not isinstance(parents[0], BTRFSVolumeDevice):
+                raise ValueError("new btrfs subvols require a parent volume")
+
+            # set up the subvol name, using mountpoint if necessary
+            if not name:
+                # 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
+        else:
+            dev_class = BTRFSVolumeDevice
+            # set up the volume label, using hostname if necessary
+            if not name:
+                hostname = ""
+                if hasattr(self.anaconda, "network"):
+                    hostname = self.anaconda.network.hostname
+
+                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")
+
+        # 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)
+        return device
+
     def createDevice(self, device):
         """ Schedule creation of a device.
 
@@ -974,71 +1031,90 @@ class Storage(object):
                 return True
         return False
 
-    def createSuggestedVGName(self, hostname=None):
-        """ Return a reasonable, unused VG name. """
-        # try to create a volume group name incorporating the hostname
+    def safeDeviceName(self, name):
+        """ Convert a device name to something safe and return that.
+
+            LVM limits lv names to 128 characters. I don't know the limits for
+            the other various device types, so I'm going to pick a number so
+            that we don't have to have an entire fucking library to determine
+            device name limits.
+        """
+        max_len = 96    # No, you don't need longer names than this. Really.
+        tmp = name.strip()
+        tmp = tmp.replace("/", "_")
+        tmp = re.sub("[^0-9a-zA-Z._-]", "", tmp)
+        tmp = tmp.lstrip("_")
+
+        if len(tmp) > max_len:
+            tmp = tmp[:max_len]
+
+        return tmp
+
+    def suggestContainerName(self, hostname=None, prefix=""):
+        """ Return a reasonable, unused device name. """
+        # try to create a device name incorporating the hostname
         if hostname not in (None, "", 'localhost', 'localhost.localdomain'):
-            template = "vg_%s" % (hostname.split('.')[0].lower(),)
-            vgtemplate = safeLvmName(template)
+            template = "%s_%s" % (prefix, hostname.split('.')[0].lower())
+            template = self.safeDeviceName(template)
         elif flags.imageInstall:
-            vgtemplate = "vg_image"
+            template = "%s_image" % prefix
         else:
-            vgtemplate = "VolGroup"
-
-        vgnames = [vg.name for vg in self.vgs]
-        if vgtemplate not in vgnames and \
-                vgtemplate not in lvm.lvm_vg_blacklist:
-            return vgtemplate
-        else:
-            i = 0
-            while 1:
-                tmpname = "%s%02d" % (vgtemplate, i,)
-                if not tmpname in vgnames and \
-                        tmpname not in lvm.lvm_vg_blacklist:
+            template = prefix
+
+        names = self.names
+        name = template
+        if name in names:
+            name = None
+            for i in range(100):
+                tmpname = "%s%02d" % (template, i,)
+                if tmpname not in names:
+                    name = tmpname
                     break
 
-                i += 1
-                if i > 99:
-                    tmpname = ""
+            if not name:
+                log.error("failed to create device name based on prefix "
+                          "'%s' and hostname '%s'" % (prefix, hostname))
+                raise RuntimeError("unable to find suitable device name")
 
-            return tmpname
+        return name
 
-    def createSuggestedLVName(self, vg, swap=None, mountpoint=None):
+    def suggestDeviceName(self, parent=None, swap=None,
+                                  mountpoint=None, prefix=""):
         """ Return a suitable, unused name for a new logical volume. """
-        # FIXME: this is not at all guaranteed to work
+        body = ""
         if mountpoint:
-            # try to incorporate the mountpoint into the name
-            if mountpoint == '/':
-                lvtemplate = 'lv_root'
+            if mountpoint == "/":
+                body = "_root"
             else:
-                if mountpoint.startswith("/"):
-                    template = "lv_%s" % mountpoint[1:]
+                body = mountpoint.replace("/", "_")
+        elif swap:
+            body = "_swap"
+
+        template = self.safeDeviceName(prefix + body)
+        names = self.names
+        name = template
+        def full_name(name, parent):
+            full = ""
+            if parent:
+                full = "%s-" % parent.name
+            full += name
+            return full
+
+        if full_name(name, parent) in names:
+            for i in range(100):
+                name = "%s%02d" % (template, i)
+                if name not in names:
+                    break
                 else:
-                    template = "lv_%s" % (mountpoint,)
+                    name = ""
 
-                lvtemplate = safeLvmName(template)
-        else:
-            if swap:
-                if len([s for s in self.swaps if s in vg.lvs]):
-                    idx = len([s for s in self.swaps if s in vg.lvs])
-                    while True:
-                        lvtemplate = "lv_swap%02d" % idx
-                        if lvtemplate in [lv.lvname for lv in vg.lvs]:
-                            idx += 1
-                        else:
-                            break
-                else:
-                    lvtemplate = "lv_swap"
-            else:
-                idx = len(vg.lvs)
-                while True:
-                    lvtemplate = "LogVol%02d" % idx
-                    if lvtemplate in [l.lvname for l in vg.lvs]:
-                        idx += 1
-                    else:
-                        break
+            if not name:
+                log.error("failed to create device name based on parent '%s', "
+                          "prefix '%s', mountpoint '%s', swap '%s'"
+                          % (parent.name, prefix, mountpoint, swap))
+                raise RuntimeError("unable to find suitable device name")
 
-        return lvtemplate
+        return name
 
     def doEncryptionPassphraseRetrofits(self):
         """ Add the global passphrase to all preexisting LUKS devices.
@@ -2183,6 +2259,7 @@ class FSSet(object):
                 device.setup()
             except Exception as msg:
                 # FIXME: need an error popup
+                log.error("setup prior to mount failed: %s" % msg)
                 continue
 
             if readOnly:
diff --git a/pyanaconda/storage/devicelibs/btrfs.py b/pyanaconda/storage/devicelibs/btrfs.py
new file mode 100644
index 0000000..aeacc47
--- /dev/null
+++ b/pyanaconda/storage/devicelibs/btrfs.py
@@ -0,0 +1,126 @@
+#
+# btrfs.py
+# btrfs functions
+#
+# Copyright (C) 2011  Red Hat, Inc.  All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): David Lehman <dlehman@redhat.com>
+#
+
+import os
+import re
+
+from pyanaconda import iutil
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import logging
+log = logging.getLogger("storage")
+
+def btrfs(args, progress=None, 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
+        kwargs["stdout"] = "/dev/tty5"
+        kwargs["progress"] = progress
+
+    ret = exec_func("btrfs", args, **kwargs)
+    if not capture and ret.rc:
+        raise BTRFSError(ret.stderr)
+
+    return ret
+
+# create goes in storage.formats.fs.BTRFS
+def create_volume(devices, label=None, data=None, metadata=None, progress=None):
+    """ For now, data and metadata must be strings mkfs.btrfs understands. """
+    if not devices:
+        raise ValueError("no devices specified")
+    elif any([not os.path.exists(d) for d in devices]):
+        raise ValueError("one or more specified devices not present")
+
+    args = []
+    if data:
+        args.append("--data=%s" % data)
+
+    if metadata:
+        args.append("--metadata=%s" % metadata)
+
+    if label:
+        args.append("--label=%s" % label)
+
+    args.extend(devices)
+
+    log.debug("mkfs.btrfs %s" % " ".join(args))    
+    ret = iutil.execWithPulseProgress("mkfs.btrfs", args,
+                                      stdout="/dev/tty5", stderr="/dev/tty5",
+                                      progress=progress)
+    if ret.rc:
+        raise BTRFSError(ret.stderr)
+
+    return ret
+
+# destroy is handled by the format class as well
+
+# add device
+#def btrfs_add_device(args
+
+# remove device
+
+def create_subvolume(mountpoint, name, progress=None):
+    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)
+
+def delete_subvolume(mountpoint, name, progress=None):
+    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)
+
+def create_snapshot(source, dest):
+    pass
+
+def scan_device(path):
+    return btrfs(["device", "scan", path])
+
+# get a list of subvolumes from a mounted btrfs filesystem
+def list_subvolumes(mountpoint):
+    if not os.path.ismount(mountpoint):
+        raise ValueError("volume not mounted")
+
+    args = ["subvol", "list", mountpoint]
+    buf = btrfs(args, capture=True)
+    vols = []
+    for group in re.findall(r'ID (\d+) top level (\d+) path (.+)\n', buf):
+        vol = {"id": int(group[0]), "path": group[2]}
+        if not vols:
+            # path None is the top level volume
+            vols.append({"id": int(group[1]), "path": None})
+
+        vols.append(vol)
+
+    return vols
diff --git a/pyanaconda/storage/devicelibs/lvm.py b/pyanaconda/storage/devicelibs/lvm.py
index 121c896..feb1216 100644
--- a/pyanaconda/storage/devicelibs/lvm.py
+++ b/pyanaconda/storage/devicelibs/lvm.py
@@ -122,21 +122,6 @@ def getMaxLVSize():
     else:
         return (16*1024*1024) #Max is 16TiB
 
-# LVM sources set the maximum length limit on VG and LV names at 128.  Set
-# our default to 2 below that to account for 0 through 99 entries we may
-# make with this name as a prefix.  LVM doesn't seem to impose a limit of
-# 99, but we do in anaconda.
-def safeLvmName(name, maxlen=126):
-    tmp = name.strip()
-    tmp = tmp.replace("/", "_")
-    tmp = re.sub("[^0-9a-zA-Z._]", "", tmp)
-    tmp = tmp.lstrip("_")
-
-    if len(tmp) > maxlen:
-        tmp = tmp[:maxlen]
-
-    return tmp
-
 def clampSize(size, pesize, roundup=None):
     if roundup:
         round = math.ceil
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
index a52e464..d8513a9 100644
--- a/pyanaconda/storage/devices.py
+++ b/pyanaconda/storage/devices.py
@@ -97,12 +97,14 @@ import os
 import math
 import copy
 import pprint
+import tempfile
 
 # device backend modules
 from devicelibs import mdraid
 from devicelibs import lvm
 from devicelibs import dm
 from devicelibs import loop
+from devicelibs import btrfs
 import parted
 import _ped
 import block
@@ -3858,3 +3860,247 @@ class NFSDevice(StorageDevice, NetworkStorageDevice):
     def destroy(self):
         """ Destroy the device. """
         log_method_call(self, self.name, status=self.status)
+
+
+class BTRFSDevice(StorageDevice):
+    """ Base class for BTRFS volume and sub-volume devices. """
+    _type = "btrfs"
+    _packages = ["btrfs-progs"]
+
+    def __init__(self, *args, **kwargs):
+        """ Passing None or no name means auto-generate one like btrfs.%d """
+        if not args or not args[0]:
+            args = ("btrfs.%d" % Device._id,)
+
+        self.uuid = kwargs.pop("uuid", None)
+
+        super(BTRFSDevice, self).__init__(*args, **kwargs)
+
+    def updateSysfsPath(self):
+        """ Update this device's sysfs path. """
+        log_method_call(self, self.name, status=self.status)
+        self.sysfsPath = self.parents[0].sysfsPath
+        log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath))
+
+    def _statusWindow(self, intf=None, title="", msg=""):
+        return self._progressWindow(intf=intf, title=title, msg=msg)
+
+    def _postCreate(self):
+        super(BTRFSDevice, self)._postCreate()
+        self.format.exists = True
+
+    def _preDestroy(self):
+        """ Preparation and precondition checking for device destruction. """
+        super(BTRFSDevice, self)._preDestroy()
+        self.setupParents(orig=True)
+
+    def _getSize(self):
+        size = sum([d.size for d in self.parents])
+        return size
+
+    def _setSize(self, size):
+        raise RuntimeError("cannot directly set size of btrfs volume")
+
+    @property
+    def status(self):
+        return not any([not d.status for d in self.parents])
+
+    @property
+    def _temp_dir_prefix(self):
+        return "btrfs-tmp.%s" % self.id
+
+    def _do_temp_mount(self):
+        if self.format.status or not self.exists:
+            return
+
+        tmpdir = tempfile.mkdtemp(prefix=self._temp_dir_prefix)
+        self.format.mount(mountpoint=tmpdir)
+
+    def _undo_temp_mount(self):
+        if self.format.status:
+            mountpoint = self.format._mountpoint
+            if os.path.basename(mountpoint).startswith(self._temp_dir_prefix):
+                self.format.unmount()
+                os.rmdir(mountpoint)
+
+    @property
+    def path(self):
+        return self.parents[0].path
+
+
+class BTRFSVolumeDevice(BTRFSDevice):
+    _type = "btrfs volume"
+    _packages = ["btrfs-progs"]
+
+    def __init__(self, *args, **kwargs):
+        self.uuid = kwargs.pop("uuid", None)
+
+        super(BTRFSDevice, self).__init__(*args, **kwargs)
+
+        self.subvolumes = []
+        self.dataLevel = None
+        self.metaDataLevel = None
+
+        for parent in self.parents:
+            if parent.format.exists and self.exists and \
+               parent.format.uuid != self.uuid:
+                raise ValueError("BTRFS member device %s UUID %s does not "
+                                 "match volume UUID %s" % (parent.name,
+                                                           parent.format.uuid,
+                                                           self.uuid))
+
+        if self.parents and not self.format.type:
+            self.format = getFormat("btrfs", exists=self.exists,
+                                    device=self.path)
+
+    def _setFormat(self, format):
+        """ Set the Device's format. """
+        super(BTRFSDevice, self)._setFormat(format)
+        _name = getattr(self.format, "label", self.uuid)
+        if not _name:
+            _name = self.id
+        self._name = "btrfs.%s" % _name
+
+    def _addDevice(self, device):
+        """ Add a new device to this volume.
+
+            XXX This is for use by device probing routines and is not
+                intended for modification of the volume.
+        """
+        log_method_call(self,
+                        self.name,
+                        device=device.name,
+                        status=self.status)
+        if not self.exists:
+            raise DeviceError("device does not exist", self.name)
+
+        if device.format.type != "btrfs":
+            raise ValueError("addDevice requires a btrfs device as sole arg")
+
+        if device.format.uuid != self.uuid:
+            raise ValueError("device UUID does not match the volume UUID")
+
+        if device in self.parents:
+            raise ValueError("device is already a member of this volume")
+
+        self.parents.append(device)
+        device.addChild()
+
+    def _removeDevice(self, device):
+        """ Remove a device from the volume.
+
+            This is for cases like clearing of preexisting partitions.
+        """
+        log_method_call(self,
+                        self.name,
+                        device=device.name,
+                        status=self.status)
+        try:
+            self.parents.remove(device)
+        except ValueError:
+            raise ValueError("cannot remove non-member device from volume")
+
+        device.removeChild()
+
+    def _addSubVolume(self, vol):
+        if vol.name in [v.name for v in self.subvolumes]:
+            raise ValueError("subvolume %s already exists" % vol.name)
+
+        self.subvolumes.append(vol)
+
+    def _removeSubVolume(self, name):
+        if name not in [v.name for v in self.subvolumes]:
+            raise ValueError("cannot remove non-existent subvolume %s" % name)
+
+        names = [v.name for v in self.subvolumes]
+        self.subvolumes.pop(names.index(name))
+
+    def findSubVolumes(self):
+        # XXX does this make sense? perhaps the devicetree should do this.
+        if self.subvolumes:
+            return
+
+        # FIXME: don't fail if the volume's already mounted
+        self.setup(orig=True)
+        try:
+            self._do_temp_mount()
+        except FSError as e:
+            log.debug("cannot list subvolumes (%s)" % e)
+            return
+
+        for vol in btrfs.list_subvolumes(self.format._mountpoint):
+            if vol["path"] is None:
+                self.vol_id = vol["id"]
+                continue
+
+            vol_id = vol["id"]
+            vol_path = vol["path"]
+            fmt = getFormat("btrfs", device=self.path, exists=True,
+                            mountopts="subvol=%d" % vol_id)
+            self.subvolumes.append(BTRFSSubVolumeDevice(vol_path,
+                                                        vol_id=vol_id,
+                                                        format=fmt))
+        self._undo_temp_mount()
+
+    def createSubVolumes(self, intf=None):
+        self._do_temp_mount()
+        for name, subvol in self.subvolumes:
+            if subvol.exists:
+                continue
+            subvolume.create(mountpoint=self._temp_dir_prefix, intf=intf)
+        self._undo_temp_mount()
+
+    def removeSubVolume(self, name):
+        raise NotImplementedError()
+
+    def _create(self, w):
+        log_method_call(self, self.name, status=self.status)
+        btrfs.create_volume(devices=[d.path for d in self.parents],
+                            label=self.format.label,
+                            data=self.dataLevel,
+                            metadata=self.metaDataLevel,
+                            progress=w)
+
+    def _destroy(self):
+        log_method_call(self, self.name, status=self.status)
+        for device in self.parents:
+            device.setup(orig=True)
+            DeviceFormat(device=device.path, exists=True).destroy()
+
+
+class BTRFSSubVolumeDevice(BTRFSDevice):
+    """ A btrfs subvolume pseudo-device. """
+    _type = "btrfs subvolume"
+    _packages = ["btrfs-progs"]
+
+    def __init__(self, *args, **kwargs):
+        self.vol_id = kwargs.pop("vol_id", None)
+        super(BTRFSSubVolumeDevice, self).__init__(*args, **kwargs)
+
+    @property
+    def volume(self):
+        return self.parents[0]
+
+    def setupParents(self, orig=False):
+        """ Run setup method of all parent devices. """
+        log_method_call(self, name=self.name, orig=orig, kids=self.kids)
+        self.volume.setup(orig=orig)
+
+    def _create(self, w):
+        log_method_call(self, self.name, status=self.status)
+        self.volume._do_temp_mount()
+        mountpoint = self.volume.format._mountpoint
+        if not mountpoint:
+            raise RuntimeError("btrfs subvol create requires mounted volume")
+
+        btrfs.create_subvolume(mountpoint, self.name, progress=w)
+        self.volume._undo_temp_mount()
+
+    def _destroy(self):
+        log_method_call(self, self.name, status=self.status)
+        self.volume._do_temp_mount()
+        mountpoint = self.volume.format._mountpoint
+        if not mountpoint:
+            raise RuntimeError("btrfs subvol destroy requires mounted volume")
+        btrfs.delete_subvolume(mountpoint, self.name)
+        self.volume._undo_temp_mount()
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py
index 5930eff..70a49a3 100644
--- a/pyanaconda/storage/devicetree.py
+++ b/pyanaconda/storage/devicetree.py
@@ -1,7 +1,7 @@
 # devicetree.py
 # Device management for anaconda's storage configuration module.
 #
-# Copyright (C) 2009  Red Hat, Inc.
+# Copyright (C) 2009, 2010, 2011  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
@@ -38,6 +38,7 @@ import devicelibs.dm
 import devicelibs.lvm
 import devicelibs.mpath
 import devicelibs.loop
+import devicelibs.btrfs
 from udev import *
 from pyanaconda import iutil
 from pyanaconda import platform
@@ -158,6 +159,9 @@ class DeviceTree(object):
         self._devices = []
         self._actions = []
 
+        # a list of all device names we encounter
+        self.names = []
+
         # indicates whether or not the tree has been fully populated
         self.populated = False
 
@@ -333,7 +337,7 @@ class DeviceTree(object):
             Raise ValueError if the device's identifier is already
             in the list.
         """
-        if newdev.path in [d.path for d in self._devices] and \
+        if newdev.uuid and newdev.uuid in [d.uuid for d in self._devices] and \
            not isinstance(newdev, NoDevice):
             raise ValueError("device is already in tree")
 
@@ -343,6 +347,12 @@ class DeviceTree(object):
                 raise DeviceTreeError("parent device not in tree")
 
         self._devices.append(newdev)
+
+        # don't include "req%d" partition names
+        if ((newdev.type != "partition" or
+             not newdev.name.startswith("req")) and
+            newdev.name not in self.names):
+            self.names.append(newdev.name)
         log.info("added %s %s (id %d) to device tree" % (newdev.type,
                                                           newdev.name,
                                                           newdev.id))
@@ -380,6 +390,8 @@ class DeviceTree(object):
                     device.updateName()
 
         self._devices.remove(dev)
+        if dev.name in self.names:
+            self.names.remove(dev.name)
         log.info("removed %s %s (id %d) from device tree" % (dev.type,
                                                               dev.name,
                                                               dev.id))
@@ -398,15 +410,16 @@ class DeviceTree(object):
         if not (action.isCreate and action.isDevice) and \
            action.device not in self._devices:
             raise DeviceTreeError("device is not in the tree")
-        elif (action.isCreate and action.isDevice):
+        #elif (action.isCreate and action.isDevice):
             # this allows multiple create actions w/o destroy in between;
             # we will clean it up before processing actions
             #raise DeviceTreeError("device is already in the tree")
+            # XXX FIXME: this is bullshit
             if action.device in self._devices:
                 self._removeDevice(action.device)
-            for d in self._devices:
-                if d.path == action.device.path:
-                    self._removeDevice(d)
+            #for d in self._devices:
+            #    if d.path == action.device.path and d.type == action.device.type:
+            #        self._removeDevice(d)
 
         if action.isCreate and action.isDevice:
             self._addDevice(action.device)
@@ -940,6 +953,10 @@ class DeviceTree(object):
         uuid = udev_device_get_uuid(info)
         sysfs_path = udev_device_get_sysfs_path(info)
 
+        # make sure we note the name of every device we see
+        if name not in self.names:
+            self.names.append(name)
+
         if self.isIgnored(info):
             log.info("ignoring %s (%s)" % (name, sysfs_path))
             if name not in self._ignoredDisks:
@@ -1362,6 +1379,10 @@ class DeviceTree(object):
             vg_device.lv_sizes.append(lv_sizes[i])
             vg_device.lv_attr.append(lv_attr[i])
 
+            name = "%s-%s" % (vg_name, lv_names[i])
+            if name not in self.names:
+                self.names.append(name)
+
     def handleUdevMDMemberFormat(self, info, device):
         log_method_call(self, name=device.name, type=device.format.type)
         # either look up or create the array device
@@ -1531,6 +1552,40 @@ class DeviceTree(object):
                 #device.format.raidmem = block.getMemFromRaidSet(dm_array,
                 #        major=major, minor=minor, uuid=uuid, name=name)
 
+    def handleBTRFSFormat(self, info, device):
+        log_method_call(self, name=device.name)
+        name = udev_device_get_name(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        uuid = udev_device_get_uuid(info)
+        sub_uuid = info.get("ID_FS_UUID_SUB")
+
+        #devicelibs.btrfs.scan_device(device.path)
+
+        btrfs_dev = None
+        for d in self.devices:
+            if isinstance(d, BTRFSDevice) and d.uuid == uuid:
+                btrfs_dev = d
+                break
+
+        if btrfs_dev:
+            log.info("found btrfs volume %s" % btrfs_dev.name)
+            btrfs_dev._addDevice(device)
+        else:
+            label = udev_device_get_label(info)
+            log.info("creating btrfs volume btrfs.%s" % label)
+            btrfs_dev = BTRFSVolumeDevice(label, parents=[device], uuid=uuid,
+                                          exists=True)
+            self._addDevice(btrfs_dev)
+
+        try:
+            btrfs_dev.findSubVolumes()
+        except StorageError as e:
+            raise
+        else:
+            for subvol in btrfs_dev.subvolumes:
+                if subvol not in self.devices:
+                    self._addDevice(subvol)
+
     def handleUdevDeviceFormat(self, info, device):
         log_method_call(self, name=getattr(device, "name", None))
         name = udev_device_get_name(info)
@@ -1609,6 +1664,15 @@ class DeviceTree(object):
                 apple = formats.getFormat("appleboot")
                 if apple.minSize <= device.size <= apple.maxSize:
                     args[0] = "appleboot"
+        elif format_type == "btrfs":
+            kwargs["uuidSub"] = info["ID_FS_UUID_SUB"]
+
+            # UUID is that of the overarching device, while UUID_SUB is the
+            # UUID of this component device
+            #
+            # member devices' format will have primary set to False, which
+            # means they cannot be modified or operated on directly
+            kwargs["primary"] = False
 
         try:
             log.info("type detected on '%s' is '%s'" % (name, format_type,))
@@ -1638,6 +1702,8 @@ class DeviceTree(object):
             self.handleUdevLVMPVFormat(info, device)
         elif device.format.type == "multipath_member":
             self.handleMultipathMemberFormat(info, device)
+        elif device.format.type == "btrfs":
+            self.handleBTRFSFormat(info, device)
 
     def updateDeviceFormat(self, device):
         log.info("updating format of device: %s" % device)
@@ -1973,7 +2039,11 @@ class DeviceTree(object):
             if device.uuid == uuid:
                 found = device
                 break
-            elif device.format.uuid == uuid:
+            elif not hasattr(device.format, "uuidSub") and \
+                 device.format.uuid == uuid:
+                found = device
+                break
+            elif getattr(device.format, "uuidSub", None) == uuid:
                 found = device
                 break
 
@@ -2024,21 +2094,30 @@ class DeviceTree(object):
         log_method_return(self, found)
         return found
 
-    def getDeviceByPath(self, path):
+    def getDeviceByPath(self, path, preferLeaves=True):
         log_method_call(self, path=path)
         if not path:
             log_method_return(self, None)
             return None
 
-        found = None
+        leaf = None
+        other = None
         for device in self._devices:
-            if device.path == path:
-                found = device
-                break
-            elif (device.type == "lvmlv" or device.type == "lvmvg") and \
-                    device.path == path.replace("--","-"):
-                found = device
-                break
+            if (device.path == path or
+                ((device.type == "lvmlv" or device.type == "lvmvg") and
+                 device.path == path.replace("--","-"))):
+                if device.isleaf and not leaf:
+                    leaf = device
+                elif not other:
+                    other = device
+
+        if preferLeaves:
+            all_devs = [leaf, other]
+        else:
+            all_devs = [other, leaf]
+        all_devs = [d for d in all_devs if d]
+        if all_devs:
+            found = all_devs[0]
 
         log_method_return(self, found)
         return found
@@ -2055,9 +2134,9 @@ class DeviceTree(object):
         """ List of device instances """
         devices = []
         for device in self._devices:
-            if device.path in [d.path for d in devices] and \
+            if device.uuid and device.uuid in [d.uuid for d in devices] and \
                not isinstance(device, NoDevice):
-                raise DeviceTreeError("duplicate paths in device tree")
+                raise DeviceTreeError("duplicate uuids in device tree")
 
             devices.append(device)
 
@@ -2087,8 +2166,13 @@ class DeviceTree(object):
             if uuid:
                 uuids[uuid] = dev
 
+            if not hasattr(dev.format, "uuidSub"):
+                attr = "uuid"
+            else:
+                attr = "uuidSub"
+
             try:
-                uuid = dev.format.uuid
+                uuid = getattr(dev.format, attr)
             except AttributeError:
                 uuid = None
 
@@ -2105,7 +2189,9 @@ class DeviceTree(object):
         """
         labels = {}
         for dev in self._devices:
-            if dev.format and getattr(dev.format, "label", None):
+            if dev.format and \
+               not hasattr(dev.format, "uuidSub") and \
+               getattr(dev.format, "label", None):
                 labels[dev.format.label] = dev
 
         return labels
diff --git a/pyanaconda/storage/formats/__init__.py b/pyanaconda/storage/formats/__init__.py
index 4b83604..4b0c122 100644
--- a/pyanaconda/storage/formats/__init__.py
+++ b/pyanaconda/storage/formats/__init__.py
@@ -144,7 +144,6 @@ class DeviceFormat(object):
     _packages = []                      # required packages
     _services = []                      # required services
     _resizable = False                  # can be resized
-    _bootable = False                   # can be used as boot
     _migratable = False                 # can be migrated
     _maxSize = 0                        # maximum size in MB
     _minSize = 0                        # minimum size in MB
@@ -371,11 +370,6 @@ class DeviceFormat(object):
         return self._resizable and self.exists
 
     @property
-    def bootable(self):
-        """ Is this format type suitable for a boot partition? """
-        return self._bootable
-
-    @property
     def migratable(self):
         """ Can formats of this type be migrated? """
         return self._migratable
diff --git a/pyanaconda/storage/formats/biosboot.py b/pyanaconda/storage/formats/biosboot.py
index 334431f..93e1de7 100644
--- a/pyanaconda/storage/formats/biosboot.py
+++ b/pyanaconda/storage/formats/biosboot.py
@@ -33,7 +33,6 @@ class BIOSBoot(DeviceFormat):
     partedFlag = PARTITION_BIOS_GRUB
     _formattable = True                 # can be formatted
     _linuxNative = True                 # for clearpart
-    _bootable = True                    # can be used as boot
     _maxSize = 2                        # maximum size in MB
     _minSize = 0.5                      # minimum size in MB
 
diff --git a/pyanaconda/storage/formats/dmraid.py b/pyanaconda/storage/formats/dmraid.py
index bfc948b..37624af 100644
--- a/pyanaconda/storage/formats/dmraid.py
+++ b/pyanaconda/storage/formats/dmraid.py
@@ -55,7 +55,6 @@ class DMRaidMember(DeviceFormat):
     _linuxNative = False                # for clearpart
     _packages = ["dmraid"]              # required packages
     _resizable = False                  # can be resized
-    _bootable = False                   # can be used as boot 
     _maxSize = 0                        # maximum size in MB
     _minSize = 0                        # minimum size in MB
     _hidden = True                      # hide devices with this formatting?
diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py
index 6f1248c..3c3229f 100644
--- a/pyanaconda/storage/formats/fs.py
+++ b/pyanaconda/storage/formats/fs.py
@@ -78,7 +78,6 @@ def fsConfigFromFile(config_file):
             formattable = True
             supported = True
             resizable = True
-            bootable = True
             linuxNative = True
             maxSize = 8 * 1024 * 1024
             minSize = 0
@@ -925,7 +924,6 @@ class Ext2FS(FS):
     _formattable = True
     _supported = True
     _resizable = True
-    _bootable = True
     _linuxNative = True
     _maxSize = 8 * 1024 * 1024
     _minSize = 0
@@ -1111,7 +1109,6 @@ class EFIFS(FATFS):
     _modules = ["vfat"]
     _name = "EFI System Partition"
     _minSize = 50
-    _bootable = True
 
     @property
     def supported(self):
@@ -1131,7 +1128,6 @@ class BTRFS(FS):
     _resizefs = "btrfsctl"
     _formattable = True
     _linuxNative = True
-    _bootable = False
     _maxLabelChars = 256
     _supported = True
     _dump = True
@@ -1139,10 +1135,28 @@ class BTRFS(FS):
     _packages = ["btrfs-progs"]
     _minSize = 256
     _maxSize = 16 * 1024 * 1024
-    # FIXME parted needs to be thaught about btrfs so that we can set the
+    # FIXME parted needs to be taught about btrfs so that we can set the
     # partition table type correctly for btrfs partitions
     # partedSystem = fileSystemType["btrfs"]
 
+    def __init__(self, *args, **kwargs):
+        self.uuidSub = kwargs.pop("uuidSub", None)
+        super(BTRFS, self).__init__(*args, **kwargs)
+
+    def create(self, *args, **kwargs):
+        # filesystem creation is done in storage.devicelibs.btrfs.create_volume
+        pass
+
+    def setup(self, *args, **kwargs):
+        log_method_call(self, type=self.mountType, device=self.device,
+                        mountpoint=self.mountpoint)
+        if not self.mountpoint and "mountpoint" not in kwargs:
+            # Since btrfs vols have subvols the format setup is automatic.
+            # Don't try to mount it if there's no mountpoint.
+            return
+
+        return self.mount(*args, **kwargs)
+
     def _getFormatOptions(self, options=None):
         argv = []
         if options and isinstance(options, list):
@@ -1328,7 +1342,6 @@ class AppleBootstrapFS(HFS):
     _type = "appleboot"
     _mountType = "hfs"
     _name = "Apple Bootstrap"
-    _bootable = True
     _minSize = 800.00 / 1024.00
     _maxSize = 1
 
@@ -1463,7 +1476,6 @@ class Iso9660FS(FS):
     _formattable = False
     _supported = True
     _resizable = False
-    _bootable = False
     _linuxNative = False
     _dump = False
     _check = False
diff --git a/pyanaconda/storage/formats/multipath.py b/pyanaconda/storage/formats/multipath.py
index 0d160e4..b77de20 100644
--- a/pyanaconda/storage/formats/multipath.py
+++ b/pyanaconda/storage/formats/multipath.py
@@ -44,7 +44,6 @@ class MultipathMember(DeviceFormat):
     _linuxNative = False                # for clearpart
     _packages = ["device-mapper-multipath"] # required packages
     _resizable = False                  # can be resized
-    _bootable = False                   # can be used as boot
     _maxSize = 0                        # maximum size in MB
     _minSize = 0                        # minimum size in MB
     _hidden = True                      # hide devices with this formatting?
diff --git a/pyanaconda/storage/formats/prepboot.py b/pyanaconda/storage/formats/prepboot.py
index 1401ee6..b7d1c60 100644
--- a/pyanaconda/storage/formats/prepboot.py
+++ b/pyanaconda/storage/formats/prepboot.py
@@ -32,7 +32,6 @@ class PPCPRePBoot(DeviceFormat):
     partedFlag = PARTITION_PREP
     _formattable = True                 # can be formatted
     _linuxNative = True                 # for clearpart
-    _bootable = True                    # can be used as boot
     _maxSize = 10                       # maximum size in MB
     _minSize = 4                        # minimum size in MB
 
diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py
index 339edec..9831704 100644
--- a/pyanaconda/storage/partitioning.py
+++ b/pyanaconda/storage/partitioning.py
@@ -68,7 +68,7 @@ def _schedulePVs(storage, disks):
     devs = []
 
     # only schedule PVs if there are LV autopart reqs
-    if not storage.lvmAutoPart:
+    if not storage.lvmAutoPart and not storage.btrfsAutoPart:
         return devs
 
     for disk in disks:
@@ -77,7 +77,10 @@ def _schedulePVs(storage, disks):
             fmt_args = {"escrow_cert": storage.autoPartEscrowCert,
                         "add_backup_passphrase": storage.autoPartAddBackupPassphrase}
         else:
-            fmt_type = "lvmpv"
+            if storage.lvmAutoPart:
+                fmt_type = "lvmpv"
+            else:
+                fmt_type = "btrfs"
             fmt_args = {}
         part = storage.newPartition(fmt_type=fmt_type,
                                                 fmt_args=fmt_args,
@@ -108,7 +111,8 @@ def _schedulePartitions(storage, disks):
     # First pass is for partitions only. We'll do LVs later.
     #
     for request in storage.autoPartitionRequests:
-        if request.asVol and storage.lvmAutoPart:
+        if (request.lv and storage.lvmAutoPart) or \
+           (request.btr and storage.btrfsAutoPart):
             continue
 
         if request.requiredSpace and request.requiredSpace > free:
@@ -177,11 +181,22 @@ def _scheduleLVs(storage, devs):
     if not devs:
         return
 
+    if storage.lvmAutoPart:
+        new_container = storage.newVG
+        new_volume = storage.newLV
+        format_name = "lvmpv"
+        parent_kw = "pvs"
+    else:
+        new_container = storage.newBTRFS
+        new_volume = storage.newBTRFS
+        format_name = "btrfs"
+        parent_kw = "parents"
+
     if storage.encryptedAutoPart:
         pvs = []
         for dev in devs:
             pv = LUKSDevice("luks-%s" % dev.name,
-                            format=getFormat("lvmpv", device=dev.path),
+                            format=getFormat(format_name, device=dev.path),
                             size=dev.size,
                             parents=dev)
             pvs.append(pv)
@@ -190,10 +205,9 @@ def _scheduleLVs(storage, devs):
         pvs = devs
 
     # create a vg containing all of the autopart pvs
-    vg = storage.newVG(pvs=pvs)
-    storage.createDevice(vg)
-
-    initialVGSize = vg.size
+    container = new_container(**{parent_kw: pvs})
+    storage.createDevice(container)
+    initialVGSize = container.size
 
     #
     # Convert storage.autoPartitionRequests into Device instances and
@@ -201,28 +215,46 @@ def _scheduleLVs(storage, devs):
     #
     # Second pass, for LVs only.
     for request in storage.autoPartitionRequests:
-        if not request.asVol or not storage.lvmAutoPart:
+        btr = storage.btrfsAutoPart and request.btr
+        lv = storage.lvmAutoPart and request.lv
+
+        if not btr and not lv:
             continue
 
-        if request.requiredSpace and request.requiredSpace > initialVGSize:
+        # required space isn't relevant on btrfs
+        if lv and \
+           request.requiredSpace and request.requiredSpace > initialVGSize:
             continue
 
         if request.fstype is None:
-            request.fstype = storage.defaultFSType
+            if btr:
+                # btrfs volumes can only contain btrfs filesystems
+                request.fstype = "btrfs"
+            else:
+                request.fstype = storage.defaultFSType
 
         # This is a little unfortunate but let the backend dictate the rootfstype
         # so that things like live installs can do the right thing
+        # XXX FIXME: yes, unfortunate. Disallow btrfs autopart on live install.
         if request.mountpoint == "/" and storage.liveImage:
+            if btr:
+                raise PartitioningError("live install can't do btrfs autopart")
+
             request.fstype = storage.liveImage.format.type
 
-        # FIXME: move this to a function and handle exceptions
-        dev = storage.newLV(vg=vg,
-                            fmt_type=request.fstype,
-                            mountpoint=request.mountpoint,
-                            grow=request.grow,
-                            maxsize=request.maxSize,
-                            size=request.size,
-                            singlePV=request.singlePV)
+        kwargs = {"mountpoint": request.mountpoint,
+                  "fmt_type": request.fstype}
+        if lv:
+            kwargs.update({"vg": container,
+                           "grow": request.grow,
+                           "maxsize": request.maxSize,
+                           "size": request.size,
+                           "singlePV": request.singlePV})
+        else:
+            kwargs.update({"parents": [container],
+                           "subvol": True})
+
+        dev = new_volume(**kwargs)
 
         # schedule the device for creation
         storage.createDevice(dev)
@@ -230,8 +262,8 @@ def _scheduleLVs(storage, devs):
 def scheduleShrinkActions(storage):
     """ Schedule actions to shrink partitions as per autopart selection. """
     for (path, size) in storage.shrinkPartitions.items():
-        device = storage.devicetree.getDeviceByPath(path)
-        if not device:
+        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))
diff --git a/pyanaconda/storage/partspec.py b/pyanaconda/storage/partspec.py
index 1602b8a..97cb33e 100644
--- a/pyanaconda/storage/partspec.py
+++ b/pyanaconda/storage/partspec.py
@@ -21,25 +21,27 @@
 
 class PartSpec(object):
     def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
-                 grow=False, asVol=False, singlePV=False, weight=0,
+                 grow=False, btr=False, lv=False, singlePV=False, weight=0,
                  requiredSpace=0, encrypted=False):
         """ Create a new storage specification.  These are used to specify
             the default partitioning layout as an object before we have the
             storage system up and running.  The attributes are obvious
             except for the following:
 
-            asVol -- Should this be allocated as a logical volume?  If not,
-                     it will be allocated as a partition.
+            btr -- Should this be allocated as a btrfs subvolume?  If not,
+                   it will be allocated as a partition.
+            lv -- Should this be allocated as a logical volume?  If not,
+                  it will be allocated as a partition.
             singlePV -- Should this logical volume map to a single physical
-                        volume in the volume group?  Implies asVol=True
+                        volume in the volume group?  Implies lv=True
             weight -- An integer that modifies the sort algorithm for partition
                       requests.  A larger value means the partition will end up
                       closer to the front of the disk.  This is mainly used to
                       make sure /boot ends up in front, and any special (PReP,
                       appleboot, etc.) partitions end up in front of /boot.
-                      This value means nothing if asVol=False.
+                      This value means nothing unless lv and btr are both False.
             requiredSpace -- This value is only taken into account if
-                             asVol=True, and specifies the size in MB that the
+                             lv=True, and specifies the size in MB that the
                              containing VG must be for this PartSpec to even
                              get used.  The VG's size is calculated before any
                              other LVs are created inside it.  If not enough
@@ -55,22 +57,24 @@ class PartSpec(object):
         self.size = size
         self.maxSize = maxSize
         self.grow = grow
-        self.asVol = asVol
+        self.lv = lv
+        self.btr = btr
         self.singlePV = singlePV
         self.weight = weight
         self.requiredSpace = requiredSpace
         self.encrypted = encrypted
 
-        if self.singlePV and not self.asVol:
-            self.asVol = True
+        if self.singlePV and not self.lv:
+            self.lv = True
 
     def __str__(self):
         s = ("%(type)s instance (%(id)s) -- \n"
-             "  mountpoint = %(mountpoint)s  asVol = %(asVol)s  singlePV = %(singlePV)s\n"
+             "  mountpoint = %(mountpoint)s  lv = %(lv)s  singlePV = %(singlePV)s"
+             "  btrfs = %(btrfs)s\n"
              "  weight = %(weight)s  fstype = %(fstype)s  encrypted = %(enc)s\n"
              "  size = %(size)s  maxSize = %(maxSize)s  grow = %(grow)s\n" %
              {"type": self.__class__.__name__, "id": "%#x" % id(self),
-              "mountpoint": self.mountpoint, "asVol": self.asVol,
+              "mountpoint": self.mountpoint, "lv": self.lv, "btrfs": self.btr,
               "singlePV": self.singlePV, "weight": self.weight,
               "fstype": self.fstype, "size": self.size, "enc": self.encrypted,
               "maxSize": self.maxSize, "grow": self.grow})
