]> git.ipfire.org Git - ipfire-3.x.git/commitdiff
pomona: New package.
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 10 Mar 2010 16:32:31 +0000 (17:32 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 10 Mar 2010 16:32:31 +0000 (17:32 +0100)
97 files changed:
pkgs/core/pomona/pomona.nm [new file with mode: 0644]
pkgs/core/pomona/src/Makefile [moved from src/pomona/Makefile with 100% similarity]
pkgs/core/pomona/src/Makefile.inc [moved from src/pomona/Makefile.inc with 98% similarity]
pkgs/core/pomona/src/autopart.py [moved from src/pomona/autopart.py with 100% similarity]
pkgs/core/pomona/src/backend.py [moved from src/pomona/backend.py with 100% similarity]
pkgs/core/pomona/src/bootloader.py [moved from src/pomona/bootloader.py with 100% similarity]
pkgs/core/pomona/src/console.py [moved from src/pomona/console.py with 100% similarity]
pkgs/core/pomona/src/constants.py [moved from src/pomona/constants.py with 100% similarity]
pkgs/core/pomona/src/cryptodev.py [moved from src/pomona/cryptodev.py with 100% similarity]
pkgs/core/pomona/src/dispatch.py [moved from src/pomona/dispatch.py with 100% similarity]
pkgs/core/pomona/src/dmraid.py [moved from src/pomona/dmraid.py with 100% similarity]
pkgs/core/pomona/src/errors.py [moved from src/pomona/errors.py with 100% similarity]
pkgs/core/pomona/src/exception.py [moved from src/pomona/exception.py with 100% similarity]
pkgs/core/pomona/src/flags.py [moved from src/pomona/flags.py with 100% similarity]
pkgs/core/pomona/src/fsset.py [moved from src/pomona/fsset.py with 100% similarity]
pkgs/core/pomona/src/installer.py [moved from src/pomona/installer.py with 100% similarity]
pkgs/core/pomona/src/instdata.py [moved from src/pomona/instdata.py with 100% similarity]
pkgs/core/pomona/src/isys/Makefile [moved from src/pomona/isys/Makefile with 98% similarity]
pkgs/core/pomona/src/isys/devices.c [moved from src/pomona/isys/devices.c with 100% similarity]
pkgs/core/pomona/src/isys/devices.h [moved from src/pomona/isys/devices.h with 100% similarity]
pkgs/core/pomona/src/isys/eddsupport.c [moved from src/pomona/isys/eddsupport.c with 100% similarity]
pkgs/core/pomona/src/isys/eddsupport.h [moved from src/pomona/isys/eddsupport.h with 100% similarity]
pkgs/core/pomona/src/isys/ethtool.c [moved from src/pomona/isys/ethtool.c with 100% similarity]
pkgs/core/pomona/src/isys/imount.c [moved from src/pomona/isys/imount.c with 100% similarity]
pkgs/core/pomona/src/isys/imount.h [moved from src/pomona/isys/imount.h with 100% similarity]
pkgs/core/pomona/src/isys/isofs.c [moved from src/pomona/isys/isofs.c with 100% similarity]
pkgs/core/pomona/src/isys/isys.c [moved from src/pomona/isys/isys.c with 100% similarity]
pkgs/core/pomona/src/isys/isys.h [moved from src/pomona/isys/isys.h with 100% similarity]
pkgs/core/pomona/src/isys/isys.py [moved from src/pomona/isys/isys.py with 100% similarity]
pkgs/core/pomona/src/isys/lang.c [moved from src/pomona/isys/lang.c with 100% similarity]
pkgs/core/pomona/src/isys/lang.h [moved from src/pomona/isys/lang.h with 100% similarity]
pkgs/core/pomona/src/isys/linkdetect.c [moved from src/pomona/isys/linkdetect.c with 100% similarity]
pkgs/core/pomona/src/isys/net.h [moved from src/pomona/isys/net.h with 100% similarity]
pkgs/core/pomona/src/isys/smp.c [moved from src/pomona/isys/smp.c with 100% similarity]
pkgs/core/pomona/src/isys/smp.h [moved from src/pomona/isys/smp.h with 100% similarity]
pkgs/core/pomona/src/isys/str.c [moved from src/pomona/isys/str.c with 100% similarity]
pkgs/core/pomona/src/isys/str.h [moved from src/pomona/isys/str.h with 100% similarity]
pkgs/core/pomona/src/isys/stubs.h [moved from src/pomona/isys/stubs.h with 100% similarity]
pkgs/core/pomona/src/isys/sundries.h [moved from src/pomona/isys/sundries.h with 100% similarity]
pkgs/core/pomona/src/isys/vio.c [moved from src/pomona/isys/vio.c with 100% similarity]
pkgs/core/pomona/src/isys/wireless.c [moved from src/pomona/isys/wireless.c with 100% similarity]
pkgs/core/pomona/src/isys/wireless.h [moved from src/pomona/isys/wireless.h with 100% similarity]
pkgs/core/pomona/src/iutil.py [moved from src/pomona/iutil.py with 100% similarity]
pkgs/core/pomona/src/keyboard_models.py [moved from src/pomona/keyboard_models.py with 100% similarity]
pkgs/core/pomona/src/lang-table [moved from src/pomona/lang-table with 100% similarity]
pkgs/core/pomona/src/lvm.py [moved from src/pomona/lvm.py with 100% similarity]
pkgs/core/pomona/src/network.py [moved from src/pomona/network.py with 100% similarity]
pkgs/core/pomona/src/packages.py [moved from src/pomona/packages.py with 100% similarity]
pkgs/core/pomona/src/pakfireinstall.py [moved from src/pomona/pakfireinstall.py with 100% similarity]
pkgs/core/pomona/src/partErrors.py [moved from src/pomona/partErrors.py with 100% similarity]
pkgs/core/pomona/src/partIntfHelpers.py [moved from src/pomona/partIntfHelpers.py with 100% similarity]
pkgs/core/pomona/src/partRequests.py [moved from src/pomona/partRequests.py with 100% similarity]
pkgs/core/pomona/src/partedUtils.py [moved from src/pomona/partedUtils.py with 100% similarity]
pkgs/core/pomona/src/partitioning.py [moved from src/pomona/partitioning.py with 100% similarity]
pkgs/core/pomona/src/partitions.py [moved from src/pomona/partitions.py with 100% similarity]
pkgs/core/pomona/src/po/Makefile [moved from src/pomona/po/Makefile with 100% similarity]
pkgs/core/pomona/src/po/da.po [moved from src/pomona/po/da.po with 100% similarity]
pkgs/core/pomona/src/po/de.po [moved from src/pomona/po/de.po with 100% similarity]
pkgs/core/pomona/src/po/pomona.pot [moved from src/pomona/po/pomona.pot with 100% similarity]
pkgs/core/pomona/src/pomona [moved from src/pomona/pomona with 100% similarity]
pkgs/core/pomona/src/pomona_log.py [moved from src/pomona/pomona_log.py with 100% similarity]
pkgs/core/pomona/src/pychecker-false-positives [moved from src/pomona/pychecker-false-positives with 100% similarity]
pkgs/core/pomona/src/raid.py [moved from src/pomona/raid.py with 100% similarity]
pkgs/core/pomona/src/runpychecker.sh [moved from src/pomona/runpychecker.sh with 100% similarity]
pkgs/core/pomona/src/scripts/getlangnames.py [moved from src/pomona/scripts/getlangnames.py with 100% similarity]
pkgs/core/pomona/src/storage_old/__init__.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/deviceaction.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devicelibs/__init__.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devicelibs/crypto.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devicelibs/lvm.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devicelibs/swap.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devices.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/devicetree.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/errors.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/formats/__init__.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/formats/fs.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/formats/luks.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/formats/lvmpv.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/formats/swap.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/partitioning.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_old/udev.py [new file with mode: 0644]
pkgs/core/pomona/src/storage_test.py [new file with mode: 0755]
pkgs/core/pomona/src/timezone.py [moved from src/pomona/timezone.py with 100% similarity]
pkgs/core/pomona/src/tui.py [moved from src/pomona/tui.py with 100% similarity]
pkgs/core/pomona/src/tui_bootloader.py [moved from src/pomona/tui_bootloader.py with 100% similarity]
pkgs/core/pomona/src/tui_complete.py [moved from src/pomona/tui_complete.py with 100% similarity]
pkgs/core/pomona/src/tui_confirm.py [moved from src/pomona/tui_confirm.py with 100% similarity]
pkgs/core/pomona/src/tui_keyboard.py [moved from src/pomona/tui_keyboard.py with 100% similarity]
pkgs/core/pomona/src/tui_language.py [moved from src/pomona/tui_language.py with 100% similarity]
pkgs/core/pomona/src/tui_network.py [moved from src/pomona/tui_network.py with 100% similarity]
pkgs/core/pomona/src/tui_partition.py [moved from src/pomona/tui_partition.py with 100% similarity]
pkgs/core/pomona/src/tui_progress.py [moved from src/pomona/tui_progress.py with 100% similarity]
pkgs/core/pomona/src/tui_timezone.py [moved from src/pomona/tui_timezone.py with 100% similarity]
pkgs/core/pomona/src/tui_userauth.py [moved from src/pomona/tui_userauth.py with 100% similarity]
pkgs/core/pomona/src/tui_welcome.py [moved from src/pomona/tui_welcome.py with 100% similarity]
pkgs/core/pomona/src/users.py [moved from src/pomona/users.py with 100% similarity]
pkgs/core/pomona/src/zonetab.py [moved from src/pomona/zonetab.py with 100% similarity]

diff --git a/pkgs/core/pomona/pomona.nm b/pkgs/core/pomona/pomona.nm
new file mode 100644 (file)
index 0000000..b5635b9
--- /dev/null
@@ -0,0 +1,64 @@
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2007, 2008 Michael Tremer & Christian Schmidt                 #
+#                                                                             #
+# 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 3 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/>.       #
+#                                                                             #
+###############################################################################
+
+###############################################################################
+# Definitions
+###############################################################################
+
+include $(PKGROOT)/Include
+
+PKG_NAME       = pomona
+PKG_VER        =
+PKG_REL        = 0
+
+PKG_MAINTAINER =
+PKG_GROUP      = System/Installer
+PKG_URL        = http://www.ipfire.org
+PKG_LICENSE    = GPLv3+
+PKG_SUMMARY    = The IPFire 3.x installer.
+
+PKG_BUILD_DEPS+= pychecker
+PKG_DEPS      += e2fsprogs parted pciutils popt \
+       pyfire python python-dbus python-parted
+
+define PKG_DESCRIPTION
+       Pomona is the installer for IPFire 3.x.
+endef
+
+DIR_APP = src
+
+define STAGE_PREPARE
+       cd $(DIR_APP) && make clean
+       #cd $(DIR_APP) && make -C po update-po
+endef
+
+define STAGE_BUILD
+       cd $(DIR_APP) && make
+endef
+
+define STAGE_TEST
+       cd $(DIR_APP) && make test
+endef
+
+define STAGE_INSTALL
+       cd $(DIR_APP) && make install DESTDIR=$(BUILDROOT) \
+               NAME=$(NAME) SNAME=$(SNAME) VERSION=$(VERSION) KVER=$(KVER)
+       cd $(DIR_APP) && make clean
+endef
similarity index 98%
rename from src/pomona/Makefile.inc
rename to pkgs/core/pomona/src/Makefile.inc
index 86830fef2f15f033cf1ea069e49b016ca197d140..998fae836e2d0d22e1772a01ca3c321c4dc9b9e2 100644 (file)
@@ -25,7 +25,7 @@ PSNAME = pomona
 
 DESTDIR=$(INSTALLER_DIR)
 
-INSTALL = /usr/bin/install -c
+INSTALL = install -c
 INSTALL_PROGRAM = ${INSTALL}
 INSTALL_DATA = ${INSTALL} -m 644
 INSTALLNLSDIR = $(DESTDIR)/usr/share/locale
similarity index 98%
rename from src/pomona/isys/Makefile
rename to pkgs/core/pomona/src/isys/Makefile
index ba0fc5bc1b28961f21183dc0ed4307ae7a2ba5b5..e462c0f999e4e2dcd69fef0d3917571f45a10fa1 100644 (file)
@@ -49,6 +49,7 @@ clean:
        rm -f depend
 
 install: all
+       -mkdir -p $(PYTHONLIBDIR)
        install -s $(PYMODULES) $(PYTHONLIBDIR)
        install -m 644 isys.py $(PYTHONLIBDIR)
 
diff --git a/pkgs/core/pomona/src/storage_old/__init__.py b/pkgs/core/pomona/src/storage_old/__init__.py
new file mode 100644 (file)
index 0000000..a6cc2e7
--- /dev/null
@@ -0,0 +1,1176 @@
+#!/usr/bin/python
+
+from devicetree import DeviceTree
+
+from deviceaction import *
+from devicelibs import lvm
+from devicelibs.lvm import safeLvmName
+from devices import *
+from formats import get_default_filesystem_type
+from udev import *
+
+from constants import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+def storageInitialize(installer):
+    storage = installer.ds.storage
+
+    storage.shutdown()
+
+    if installer.dispatch.dir == DISPATCH_BACK:
+        return
+
+    # XXX I don't understand why I have to do this
+    udev_trigger(subsystem="block")
+
+    #XXX Determine our cdrom drive/usb key here and add it to protectedPartiotions
+    storage.reset()
+
+class Storage(object):
+    def __init__(self, installer):
+        self.installer = installer
+
+        self.protectedDisks = []
+        self.clearDisks = []
+        self.ignoredDisks = []
+
+        self.defaultFSType = get_default_filesystem_type()
+        self.defaultBootFSType = get_default_filesystem_type(boot=True)
+
+        self.doAutoPartition = False
+        self.encryptedAutoPart = False
+        #self.autoPartitionRequests = []
+        self.autoPartitionRequests = [PartSpec(mountpoint="/", fstype=self.defaultFSType, size=1024, grow=True),
+                                      PartSpec(mountpoint="/boot", fstype=self.defaultFSType, size=75, grow=False),]
+
+        #self.devicetree = DeviceTree(self.installer)
+        self.devicetree = None
+
+        self._nextID = 0
+
+    def shutdown(self):
+        self.installer.log.debug("Shutting down storage...")
+
+    def reset(self):
+        """ Reset storage configuration to reflect actual system state.
+
+            This should rescan from scratch but not clobber user-obtained
+            information like passphrases
+        """
+        #for device in self.devices:
+        #    if device.format.type == "luks" and device.format.exists:
+        #        self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase
+
+        self.installer.window = self.installer.intf.waitWindow(_("Finding Devices"),
+                                    _("Finding storage devices..."))
+        self.devicetree = DeviceTree(self.installer)
+        self.devicetree.populate()
+        self.fsset = FSSet(self.installer)
+        self.installer.window.pop()
+
+    def checkNoDisks(self):
+        """Check that there are valid disk devices."""
+        if not self.disks:
+            self.installer.intf.messageWindow(_("No Drives Found"),
+                               _("An error has occurred - no valid devices were "
+                                 "found on which to create new file systems. "
+                                 "Please check your hardware for the cause "
+                                 "of this problem."))
+            return True
+        return False
+
+    def sanityCheck(self):
+        """ Run a series of tests to verify the storage configuration.
+
+            This function is called at the end of partitioning so that
+            we can make sure you don't have anything silly (like no /,
+            a really small /, etc).  Returns (errors, warnings) where
+            each is a list of strings.
+        """
+        checkSizes = [('/usr', 250), ('/tmp', 50), ('/var', 384),
+                      ('/home', 100), ('/boot', 75)]
+        warnings = []
+        errors = []
+
+        filesystems = self.fsset.mountpoints
+        root = self.fsset.rootDevice
+        swaps = self.fsset.swapDevices
+        #try:
+        #    boot = self.anaconda.platform.bootDevice()
+        #except DeviceError:
+        #    boot = None
+        boot = None
+
+        if not root:
+            errors.append(_("You have not defined a root partition (/), "
+                            "which is required for installation of %s "
+                            "to continue.") % (PRODUCT_NAME,))
+
+        if root and root.size < 250:
+            warnings.append(_("Your root partition is less than 250 "
+                              "megabytes which is usually too small to "
+                              "install %s.") % (PRODUCT_NAME,))
+
+        recommended_size = 1024
+        if (root and root.size < recommended_size):
+            errors.append(_("Your / partition is less than %s "
+                            "megabytes which is lower than recommended "
+                            "for a normal %s install.")
+                          %(recommended_size, PRODUCT_NAME))
+
+        # livecds have to have the rootfs type match up
+        #if (root and
+        #    self.installer.backend.rootFsType and
+        #    root.format.type != self.installer.backend.rootFsType):
+        #    errors.append(_("Your / partition does not match the "
+        #                    "the live image you are installing from.  "
+        #                    "It must be formatted as %s.")
+        #                  % (self.anaconda.backend.rootFsType,))
+
+        for (mount, size) in checkSizes:
+            if mount in filesystems and filesystems[mount].size < size:
+                warnings.append(_("Your %s partition is less than %s "
+                                  "megabytes which is lower than recommended "
+                                  "for a normal %s install.")
+                                %(mount, size, PRODUCT_NAME))
+
+        usb_disks = []
+        firewire_disks = []
+        #for disk in self.disks:
+        #    if isys.driveUsesModule(disk.name, ["usb-storage", "ub"]):
+        #        usb_disks.append(disk)
+        #    elif isys.driveUsesModule(disk.name, ["sbp2", "firewire-sbp2"]):
+        #        firewire_disks.append(disk)
+
+        uses_usb = False
+        uses_firewire = False
+        for device in filesystems.values():
+            for disk in usb_disks:
+                if device.dependsOn(disk):
+                    uses_usb = True
+                    break
+
+            for disk in firewire_disks:
+                if device.dependsOn(disk):
+                    uses_firewire = True
+                    break
+
+        if uses_usb:
+            warnings.append(_("Installing on a USB device.  This may "
+                              "or may not produce a working system."))
+        if uses_firewire:
+            warnings.append(_("Installing on a FireWire device.  This may "
+                              "or may not produce a working system."))
+
+        if not boot:
+            errors.append(_("You have not created a boot partition."))
+
+        if (boot and boot.type == "mdarray" and
+            boot.level != 1):
+            errors.append(_("Bootable partitions can only be on RAID1 "
+                            "devices."))
+
+        # can't have bootable partition on LV
+        if boot and boot.type == "lvmlv":
+            errors.append(_("Bootable partitions cannot be on a "
+                            "logical volume."))
+
+        # most arches can't have boot on RAID
+        if boot and boot.type == "mdarray" and not self.anaconda.platform.supportsMdRaidBoot:
+            errors.append(_("Bootable partitions cannot be on a RAID "
+                            "device."))
+
+        # Lots of filesystems types don't support /boot.
+        if boot and not boot.format.bootable:
+            errors.append(_("Bootable partitions cannot be on an %s "
+                            "filesystem.") % boot.format.name)
+
+        # vfat /boot is insane.
+        if (boot and boot == root and boot.format.type == "vfat"):
+            errors.append(_("Bootable partitions cannot be on an %s "
+                            "filesystem.") % boot.format.type)
+
+        if (boot and filter(lambda d: d.type == "luks/dm-crypt",
+                            self.deviceDeps(boot))):
+            errors.append(_("Bootable partitions cannot be on an "
+                            "encrypted block device"))
+
+        if not swaps:
+            warnings.append(_("You have not specified a swap partition.  "
+                              "Although not strictly required in all cases, "
+                              "it will significantly improve performance for "
+                              "most installations."))
+
+        return (errors, warnings)
+
+    def deviceDeps(self, device):
+        return self.devicetree.getDependentDevices(device)
+
+    @property
+    def nextID(self):
+        id = self._nextID
+        self._nextID += 1
+        return id
+
+    @property
+    def disks(self):
+        """ A list of the disks in the device tree.
+
+            Ignored disks are not included, as are disks with no media present.
+
+            This is based on the current state of the device tree and
+            does not necessarily reflect the actual on-disk state of the
+            system's disks.
+        """
+        disks = []
+        devices = self.devicetree.devices
+        for d in devices:
+            if isinstance(devices[d], DiskDevice) and devices[d].mediaPresent:
+                disks.append(devices[d])
+        disks.sort(key=lambda d: d.name)
+        return disks
+
+    @property
+    def devices(self):
+        """ A list of all the devices in the device tree. """
+        devices = self.devicetree.devices.values()
+        devices.sort(key=lambda d: d.path)
+        return devices
+
+    @property
+    def partitions(self):
+        """ A list of the partitions in the device tree.
+
+            This is based on the current state of the device tree and
+            does not necessarily reflect the actual on-disk state of the
+            system's disks.
+        """
+        partitions = self.devicetree.getDevicesByInstance(PartitionDevice)
+        partitions.sort(key=lambda d: d.name)
+        return partitions
+
+    @property
+    def vgs(self):
+        """ A list of the LVM Volume Groups in the device tree.
+
+            This is based on the current state of the device tree and
+            does not necessarily reflect the actual on-disk state of the
+            system's disks.
+        """
+        vgs = self.devicetree.getDevicesByType("lvmvg")
+        vgs.sort(key=lambda d: d.name)
+        return vgs
+
+    def createDevice(self, device):
+        """ Schedule creation of a device.
+
+            TODO: We could do some things here like assign the next
+                  available raid minor if one isn't already set.
+        """
+        self.devicetree.registerAction(ActionCreateDevice(self.installer, device))
+        if device.format.type:
+            self.devicetree.registerAction(ActionCreateFormat(self.installer, device))
+
+    def destroyDevice(self, device):
+        """ Schedule destruction of a device. """
+        if device.format.exists and device.format.type:
+            # schedule destruction of any formatting while we're at it
+            self.devicetree.registerAction(ActionDestroyFormat(self.installer, device))
+
+        action = ActionDestroyDevice(self.installer, device)
+        self.devicetree.registerAction(action)
+
+    def newPartition(self, *args, **kwargs):
+        """ Return a new PartitionDevice instance for configuring. """
+        if kwargs.has_key("fmt_type"):
+            kwargs["format"] = getFormat(kwargs.pop("fmt_type"), installer=self.installer,
+                                         mountpoint=kwargs.pop("mountpoint", None))
+
+        if kwargs.has_key("disks"):
+            parents = kwargs.pop("disks")
+            if isinstance(parents, Device):
+                kwargs["parents"] = [parents]
+            else:
+                kwargs["parents"] = parents
+
+        if kwargs.has_key("name"):
+            name = kwargs.pop("name")
+        else:
+            name = "req%d" % self.nextID
+
+        return PartitionDevice(self.installer, name, *args, **kwargs)
+
+    def newVG(self, *args, **kwargs):
+        """ Return a new LVMVolumeGroupDevice instance. """
+        pvs = kwargs.pop("pvs", [])
+        for pv in pvs:
+            if pv not in self.devices:
+                raise ValueError("pv is not in the device tree")
+
+        if kwargs.has_key("name"):
+            name = kwargs.pop("name")
+        else:
+            # XXX name = self.createSuggestedVGName(self.anaconda.id.network)
+            name = self.createSuggestedVGName(None)
+
+        if name in [d.name for d in self.devices]:
+            raise ValueError("name already in use")
+
+        return LVMVolumeGroupDevice(self.installer, name, pvs, *args, **kwargs)
+
+    def newLV(self, *args, **kwargs):
+        """ Return a new LVMLogicalVolumeDevice instance. """
+        if kwargs.has_key("vg"):
+            vg = kwargs.pop("vg")
+
+        mountpoint = kwargs.pop("mountpoint", None)
+        if kwargs.has_key("fmt_type"):
+            kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
+                                         installer=self.installer,
+                                         mountpoint=mountpoint)
+
+        if kwargs.has_key("name"):
+            name = kwargs.pop("name")
+        else:
+            if kwargs.get("format") and kwargs["format"].type == "swap":
+                swap = True
+            else:
+                swap = False
+            name = self.createSuggestedLVName(vg,
+                                              swap=swap,
+                                              mountpoint=mountpoint)
+
+        if name in [d.name for d in self.devices]:
+            raise ValueError("Name already in use")
+
+        return LVMLogicalVolumeDevice(self.installer, name, vg, *args, **kwargs)
+
+    def createSuggestedVGName(self, network):
+        """ Return a reasonable, unused VG name. """
+        # try to create a volume group name incorporating the hostname
+        #hn = network.hostname # XXX
+        hn = "%s.localdomain" % PROCUCT_SNAME
+        vgnames = [vg.name for vg in self.vgs]
+        if hn is not None and hn != '':
+            if hn == 'localhost' or hn == 'localhost.localdomain':
+                vgtemplate = "VolGroup"
+            elif hn.find('.') != -1:
+                hn = safeLvmName(hn)
+                vgtemplate = "vg_%s" % (hn.split('.')[0].lower(),)
+            else:
+                hn = safeLvmName(hn)
+                vgtemplate = "vg_%s" % (hn.lower(),)
+        else:
+            vgtemplate = "VolGroup"
+
+        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:
+                    break
+
+                i += 1
+                if i > 99:
+                    tmpname = ""
+
+            return tmpname
+
+    def createSuggestedLVName(self, vg, swap=None, mountpoint=None):
+        """ Return a suitable, unused name for a new logical volume. """
+        # FIXME: this is not at all guaranteed to work
+        if mountpoint:
+            # try to incorporate the mountpoint into the name
+            if mountpoint == '/':
+                lvtemplate = 'lv_root'
+            else:
+                tmp = safeLvmName(mountpoint)
+                lvtemplate = "lv_%s" % (tmp,)
+        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
+
+        return lvtemplate
+
+    def deviceImmutable(self, device):
+        """ Return any reason the device cannot be modified/removed.
+
+            Return False if the device can be removed.
+
+            Devices that cannot be removed include:
+
+                - protected partitions
+                - devices that are part of an md array or lvm vg
+                - extended partition containing logical partitions that
+                  meet any of the above criteria
+
+        """
+        if not isinstance(device, Device):
+            raise ValueError("arg1 (%s) must be a Device instance" % device)
+
+        if device.name in self.protectedDisks:
+            return _("This partition is holding the data for the hard "
+                      "drive install.")
+        elif device.format.type == "mdmember":
+            for array in self.mdarrays:
+                if array.dependsOn(device):
+                    if array.minor is not None:
+                        return _("This device is part of the RAID "
+                                 "device %s.") % (array.path,)
+                    else:
+                        return _("This device is part of a RAID device.")
+        elif device.format.type == "lvmpv":
+            for vg in self.vgs:
+                if vg.dependsOn(device):
+                    if vg.name is not None:
+                        return _("This device is part of the LVM "
+                                 "volume group '%s'.") % (vg.name,)
+                    else:
+                        return _("This device is part of a LVM volume "
+                                 "group.")
+        elif device.format.type == "luks":
+            try:
+                luksdev = self.devicetree.getChildren(device)[0]
+            except IndexError:
+                pass
+            else:
+                return self.deviceImmutable(luksdev)
+        elif isinstance(device, PartitionDevice) and device.isExtended:
+            reasons = {}
+            for dep in self.deviceDeps(device):
+                reason = self.deviceImmutable(dep)
+                if reason:
+                    reasons[dep.path] = reason
+            if reasons:
+                msg =  _("This device is an extended partition which "
+                         "contains logical partitions that cannot be "
+                         "deleted:\n\n")
+                for dev in reasons:
+                    msg += "%s: %s" % (dev, reasons[dev])
+                return msg
+
+        for i in self.devicetree.immutableDevices:
+            if i[0] == device.name:
+                return i[1]
+
+        return False
+
+
+class FSSet(object):
+    """ A class to represent a set of filesystems. """
+    def __init__(self, installer):
+        self.installer = installer
+        self.devicetree = installer.ds.storage.devicetree
+        self.cryptTab = None
+        self.blkidTab = None
+        self.origFStab = None
+        self.active = False
+        self._dev = None
+        self._devpts = None
+        self._sysfs = None
+        self._proc = None
+        self._devshm = None
+
+    @property
+    def sysfs(self):
+        if not self._sysfs:
+            self._sysfs = NoDevice(format=getFormat("sysfs",
+                                                    device="sys",
+                                                    mountpoint="/sys"))
+        return self._sysfs
+
+    @property
+    def dev(self):
+        if not self._dev:
+            self._dev = DirectoryDevice("/dev", format=getFormat("bind",
+                                                                 device="/dev",
+                                                                 mountpoint="/dev",
+                                                                 exists=True),
+                                        exists=True)
+
+        return self._dev
+
+    @property
+    def devpts(self):
+        if not self._devpts:
+            self._devpts = NoDevice(format=getFormat("devpts",
+                                                     device="devpts",
+                                                     mountpoint="/dev/pts"))
+        return self._devpts
+
+    @property
+    def proc(self):
+        if not self._proc:
+            self._proc = NoDevice(format=getFormat("proc",
+                                                   device="proc",
+                                                   mountpoint="/proc"))
+        return self._proc
+
+    @property
+    def devshm(self):
+        if not self._devshm:
+            self._devshm = NoDevice(format=getFormat("tmpfs",
+                                                     device="tmpfs",
+                                                     mountpoint="/dev/shm"))
+        return self._devshm
+
+    @property
+    def devices(self):
+        devices = self.devicetree.devices.values()
+        devices.sort(key=lambda d: d.path)
+        return devices
+
+    @property
+    def mountpoints(self):
+        filesystems = {}
+        for device in self.devices:
+            if device.format.mountable and device.format.mountpoint:
+                filesystems[device.format.mountpoint] = device
+        return filesystems
+
+    def _parseOneLine(self, (devspec, mountpoint, fstype, options, dump, passno)):
+        # find device in the tree
+        device = self.devicetree.resolveDevice(devspec,
+                                               cryptTab=self.cryptTab,
+                                               blkidTab=self.blkidTab)
+        if device:
+            # fall through to the bottom of this block
+            pass
+        elif devspec.startswith("/dev/loop"):
+            # FIXME: create devices.LoopDevice
+            self.installer.log.warning("completely ignoring your loop mount")
+        elif ":" in devspec:
+            # NFS -- preserve but otherwise ignore
+            device = NFSDevice(devspec,
+                               format=getFormat(fstype,
+                                                device=devspec))
+        elif devspec.startswith("/") and fstype == "swap":
+            # swap file
+            device = FileDevice(devspec,
+                                parents=get_containing_device(devspec, self.devicetree),
+                                format=getFormat(fstype,
+                                                 device=devspec,
+                                                 exists=True),
+                                exists=True)
+        elif fstype == "bind" or "bind" in options:
+            # bind mount... set fstype so later comparison won't
+            # turn up false positives
+            fstype = "bind"
+            device = FileDevice(devspec,
+                                parents=get_containing_device(devspec, self.devicetree),
+                                exists=True)
+            device.format = getFormat("bind",
+                                      device=device.path,
+                                      exists=True)
+        elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts"):
+            # drop these now -- we'll recreate later
+            return None
+        else:
+            # nodev filesystem -- preserve or drop completely?
+            format = getFormat(fstype)
+            if devspec == "none" or \
+               isinstance(format, get_device_format_class("nodev")):
+                device = NoDevice(format)
+            else:
+                device = StorageDevice(devspec)
+
+        if device is None:
+            self.installer.log.error("failed to resolve %s (%s) from fstab" % (devspec,
+                                                                               fstype))
+            return None
+
+        # make sure, if we're using a device from the tree, that
+        # the device's format we found matches what's in the fstab
+        fmt = getFormat(fstype, device=device.path)
+        if fmt.type != device.format.type:
+            self.installer.log.warning("scanned format (%s) differs from fstab "
+                                       "format (%s)" % (device.format.type, fstype))
+
+        if device.format.mountable:
+            device.format.mountpoint = mountpoint
+            device.format.mountopts = options
+
+        # is this useful?
+        try:
+            device.format.options = options
+        except AttributeError:
+            pass
+
+        return device
+
+    def parseFSTab(self, chroot=""):
+        """ parse /etc/fstab
+
+            preconditions:
+                all storage devices have been scanned, including filesystems
+            postconditions:
+
+            FIXME: control which exceptions we raise
+
+            XXX do we care about bind mounts?
+                how about nodev mounts?
+                loop mounts?
+        """
+        if not chroot or not os.path.isdir(chroot):
+            chroot = ""
+
+        path = "%s/etc/fstab" % chroot
+        if not os.access(path, os.R_OK):
+            # XXX should we raise an exception instead?
+            self.installer.log.info("cannot open %s for read" % path)
+            return
+
+        blkidTab = BlkidTab(self.installer, chroot=chroot)
+        try:
+            blkidTab.parse()
+            self.installer.log.debug("blkid.tab devs: %s" % blkidTab.devices.keys())
+        except Exception as e:
+            self.installer.log.info("error parsing blkid.tab: %s" % e)
+            blkidTab = None
+
+        cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
+        try:
+            cryptTab.parse(chroot=chroot)
+            self.installer.log.debug("crypttab maps: %s" % cryptTab.mappings.keys())
+        except Exception as e:
+            self.installer.log.info("error parsing crypttab: %s" % e)
+            cryptTab = None
+
+        self.blkidTab = blkidTab
+        self.cryptTab = cryptTab
+
+        with open(path) as f:
+            self.installer.log.debug("parsing %s" % path)
+
+            lines = f.readlines()
+
+            # save the original file
+            self.origFStab = ''.join(lines)
+
+            for line in lines:
+                # strip off comments
+                (line, pound, comment) = line.partition("#")
+                fields = line.split()
+
+                if not 4 <= len(fields) <= 6:
+                    continue
+                elif len(fields) == 4:
+                    fields.extend([0, 0])
+                elif len(fields) == 5:
+                    fields.append(0)
+
+                (devspec, mountpoint, fstype, options, dump, passno) = fields
+
+                try:
+                    device = self._parseOneLine((devspec, mountpoint, fstype, options, dump, passno))
+                except Exception as e:
+                    raise Exception("fstab entry %s is malformed: %s" % (devspec, e))
+
+                if not device:
+                    continue
+
+                if device not in self.devicetree.devices.values():
+                    self.devicetree._addDevice(device)
+
+    def fsFreeSpace(self, chroot='/'):
+        space = []
+        for device in self.devices:
+            if not device.format.mountable or \
+               not device.format.status:
+                continue
+
+            path = "%s/%s" % (chroot, device.format.mountpoint)
+            try:
+                space.append((device.format.mountpoint,
+                              isys.pathSpaceAvailable(path)))
+            except SystemError:
+                self.installer.log.error("failed to calculate free space for %s" % (device.format.mountpoint,))
+
+        space.sort(key=lambda s: s[1])
+        return space
+
+    def mtab(self):
+        format = "%s %s %s %s 0 0\n"
+        mtab = ""
+        devices = self.mountpoints.values() + self.swapDevices
+        devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
+        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+        for device in devices:
+            if not device.format.status:
+                continue
+            if not device.format.mountable:
+                continue
+            if device.format.mountpoint:
+                options = device.format.mountopts
+                if options:
+                    options = options.replace("defaults,", "")
+                    options = options.replace("defaults", "")
+
+                if options:
+                    options = "rw," + options
+                else:
+                    options = "rw"
+                mtab = mtab + format % (device.path,
+                                        device.format.mountpoint,
+                                        device.format.type,
+                                        options)
+        return mtab
+
+    def turnOnSwap(self):
+        intf = self.installer.intf
+        for device in self.swapDevices:
+            try:
+                device.setup()
+                device.format.setup()
+            except SuspendError:
+                if intf:
+                    msg = _("The swap device:\n\n     %s\n\n"
+                            "in your /etc/fstab file is currently in "
+                            "use as a software suspend device, "
+                            "which means your system is hibernating. "
+                            "If you are performing a new install, "
+                            "make sure the installer is set "
+                            "to format all swap devices.") \
+                            % device.path
+                    intf.messageWindow(_("Error"), msg)
+                sys.exit(0)
+            except DeviceError as msg:
+                if intf:
+                    err = _("Error enabling swap device %s: %s\n\n"
+                            "This most likely means this swap "
+                            "device has not been initialized.\n\n"
+                            "Press OK to exit the installer.") % \
+                            (device.path, msg)
+                    intf.messageWindow(_("Error"), err)
+                sys.exit(0)
+
+    def mountFilesystems(self, installer, raiseErrors=None, readOnly=None,
+                         skipRoot=False):
+        intf = installer.intf
+        devices = self.mountpoints.values() + self.swapDevices
+        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
+        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+
+        for device in devices:
+            if not device.format.mountable or not device.format.mountpoint:
+                continue
+
+            if skipRoot and device.format.mountpoint == "/":
+                continue
+
+            options = device.format.options
+            if "noauto" in options.split(","):
+                continue
+
+            try:
+                device.setup()
+            except Exception as msg:
+                # FIXME: need an error popup
+                continue
+
+            if readOnly:
+                options = "%s,%s" % (options, readOnly)
+
+            try:
+                device.format.setup(options=options,
+                                    chroot=installer.rootPath)
+            except OSError as (num, msg):
+                if intf:
+                    if num == errno.EEXIST:
+                        intf.messageWindow(_("Invalid mount point"),
+                                           _("An error occurred when trying "
+                                             "to create %s.  Some element of "
+                                             "this path is not a directory. "
+                                             "This is a fatal error and the "
+                                             "install cannot continue.\n\n"
+                                             "Press <Enter> to exit the "
+                                             "installer.")
+                                           % (device.format.mountpoint,))
+                    else:
+                        intf.messageWindow(_("Invalid mount point"),
+                                           _("An error occurred when trying "
+                                             "to create %s: %s.  This is "
+                                             "a fatal error and the install "
+                                             "cannot continue.\n\n"
+                                             "Press <Enter> to exit the "
+                                             "installer.")
+                                            % (device.format.mountpoint, msg))
+                self.installer.log.error("OSError: (%d) %s" % (num, msg) )
+                sys.exit(0)
+            except SystemError as (num, msg):
+                if raiseErrors:
+                    raise
+                if intf and not device.format.linuxNative:
+                    ret = intf.messageWindow(_("Unable to mount filesystem"),
+                                             _("An error occurred mounting "
+                                             "device %s as %s.  You may "
+                                             "continue installation, but "
+                                             "there may be problems.") %
+                                             (device.path,
+                                              device.format.mountpoint),
+                                             type="custom",
+                                             custom_icon="warning",
+                                             custom_buttons=[_("_Exit installer"),
+                                                            _("_Continue")])
+
+                    if ret == 0:
+                        sys.exit(0)
+                    else:
+                        continue
+
+                self.installer.log.error("SystemError: (%d) %s" % (num, msg) )
+                sys.exit(0)
+            except FSError as msg:
+                if intf:
+                    intf.messageWindow(_("Unable to mount filesystem"),
+                                       _("An error occurred mounting "
+                                         "device %s as %s: %s. This is "
+                                         "a fatal error and the install "
+                                         "cannot continue.\n\n"
+                                         "Press <Enter> to exit the "
+                                         "installer.")
+                                        % (device.path,
+                                           device.format.mountpoint,
+                                           msg))
+                self.installer.log.error("FSError: %s" % msg)
+                sys.exit(0)
+
+        self.active = True
+
+    def umountFilesystems(self, instPath, ignoreErrors=True, swapoff=True):
+        devices = self.mountpoints.values() + self.swapDevices
+        devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
+        devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+        devices.reverse()
+        for device in devices:
+            if not device.format.mountable and \
+               (device.format.type != "swap" or swapoff):
+                continue
+
+            device.format.teardown()
+            device.teardown()
+
+        self.active = False
+
+    def createSwapFile(self, rootPath, device, size):
+        """ Create and activate a swap file under rootPath. """
+        filename = "/SWAP"
+        count = 0
+        basedir = os.path.normpath("%s/%s" % (rootPath,
+                                              device.format.mountpoint))
+        while os.path.exists("%s/%s" % (basedir, filename)) or \
+              self.devicetree.getDeviceByName(filename):
+            file = os.path.normpath("%s/%s" % (basedir, filename))
+            count += 1
+            filename = "/SWAP-%d" % count
+
+        dev = FileDevice(filename,
+                         size=size,
+                         parents=[device],
+                         format=getFormat("swap", device=filename))
+        dev.create()
+        dev.setup()
+        dev.format.create()
+        dev.format.setup()
+        # nasty, nasty
+        self.devicetree._addDevice(dev)
+
+    def mkDevRoot(self, instPath):
+        root = self.rootDevice
+        dev = "%s/%s" % (instPath, root.path)
+        if not os.path.exists("%s/dev/root" %(instPath,)) and os.path.exists(dev):
+            rdev = os.stat(dev).st_rdev
+            os.mknod("%s/dev/root" % (instPath,), stat.S_IFBLK | 0600, rdev)
+
+    @property
+    def swapDevices(self):
+        swaps = []
+        for device in self.devices:
+            if device.format.type == "swap":
+                swaps.append(device)
+        return swaps
+
+    @property
+    def rootDevice(self):
+        for device in self.devices:
+            try:
+                mountpoint = device.format.mountpoint
+            except AttributeError:
+                mountpoint = None
+
+            if mountpoint == "/":
+                return device
+
+    @property
+    def migratableDevices(self):
+        """ List of devices whose filesystems can be migrated. """
+        migratable = []
+        for device in self.devices:
+            if device.format.migratable and device.format.exists:
+                migratable.append(device)
+
+        return migratable
+
+    def write(self, instPath):
+        """ write out all config files based on the set of filesystems """
+        # /etc/fstab
+        fstab_path = os.path.normpath("%s/etc/fstab" % instPath)
+        fstab = self.fstab()
+        open(fstab_path, "w").write(fstab)
+
+        # /etc/crypttab
+        crypttab_path = os.path.normpath("%s/etc/crypttab" % instPath)
+        crypttab = self.crypttab()
+        open(crypttab_path, "w").write(crypttab)
+
+        # /etc/mdadm.conf
+        mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % instPath)
+        mdadm_conf = self.mdadmConf()
+        open(mdadm_path, "w").write(mdadm_conf)
+
+    def crypttab(self):
+        # if we are upgrading, do we want to update crypttab?
+        # gut reaction says no, but plymouth needs the names to be very
+        # specific for passphrase prompting
+        if not self.cryptTab:
+            self.cryptTab = CryptTab(self.devicetree)
+            self.cryptTab.populate()
+
+        devices = self.mountpoints.values() + self.swapDevices
+
+        # prune crypttab -- only mappings required by one or more entries
+        for name in self.cryptTab.mappings.keys():
+            keep = False
+            mapInfo = self.cryptTab[name]
+            cryptoDev = mapInfo['device']
+            for device in devices:
+                if device == cryptoDev or device.dependsOn(cryptoDev):
+                    keep = True
+                    break
+
+            if not keep:
+                del self.cryptTab.mappings[name]
+
+        return self.cryptTab.crypttab()
+
+    def mdadmConf(self):
+        """ Return the contents of mdadm.conf. """
+        arrays = self.devicetree.getDevicesByType("mdarray")
+        conf = ""
+        devices = self.mountpoints.values() + self.swapDevices
+        for array in arrays:
+            writeConf = False
+            for device in devices:
+                if device == array or device.dependsOn(array):
+                    writeConf = True
+                    break
+
+            if writeConf:
+                conf += array.mdadmConfEntry
+
+        return conf
+
+    def fstab (self):
+        format = "%-23s %-23s %-7s %-15s %d %d\n"
+        fstab = """
+#
+# /etc/fstab
+# Created by pomona on %s
+#
+# Accessible filesystems, by reference, are maintained under '/dev/disk'
+# See man pages fstab(5), findfs(8), mount(8) and/or vol_id(8) for more info
+#
+""" % time.asctime()
+
+        devices = self.mountpoints.values() + self.swapDevices
+        devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
+        netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
+        for device in devices:
+            # why the hell do we put swap in the fstab, anyway?
+            if not device.format.mountable and device.format.type != "swap":
+                continue
+
+            fstype = device.format.type
+            if fstype == "swap":
+                mountpoint = "swap"
+                options = device.format.options
+            else:
+                mountpoint = device.format.mountpoint
+                options = device.format.mountopts
+                if not mountpoint:
+                    self.installer.log.warning("%s filesystem on %s has no mountpoint" % \
+                                               (fstype, device.path))
+                    continue
+
+            options = options or "defaults"
+            for netdev in netdevs:
+                if device.dependsOn(netdev):
+                    options = options + ",_netdev"
+                    break
+            devspec = device.fstabSpec
+            dump = device.format.dump
+            if device.format.check and mountpoint == "/":
+                passno = 1
+            elif device.format.check:
+                passno = 2
+            else:
+                passno = 0
+            fstab = fstab + device.fstabComment
+            fstab = fstab + format % (devspec, mountpoint, fstype,
+                                      options, dump, passno)
+        return fstab
+
+class PartSpec(object):
+    def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
+                 grow=False, asVol=False, weight=0):
+        self.mountpoint = mountpoint
+        self.fstype = fstype
+        self.size = size
+        self.maxSize = maxSize
+        self.grow = grow
+        self.asVol = asVol
+        self.weight = weight
+
+
+class BlkidTab(object):
+    """ Dictionary-like interface to blkid.tab with device path keys """
+    def __init__(self, installer, chroot=""):
+        self.installer = installer
+        self.chroot = chroot
+        self.devices = {}
+
+    def parse(self):
+        path = "%s/etc/blkid/blkid.tab" % self.chroot
+        self.installer.log.debug("parsing %s" % path)
+        with open(path) as f:
+            for line in f.readlines():
+                # this is pretty ugly, but an XML parser is more work than
+                # is justifiable for this purpose
+                if not line.startswith("<device "):
+                    continue
+
+                line = line[len("<device "):-len("</device>\n")]
+                (data, sep, device) = line.partition(">")
+                if not device:
+                    continue
+
+                self.devices[device] = {}
+                for pair in data.split():
+                    try:
+                        (key, value) = pair.split("=")
+                    except ValueError:
+                        continue
+
+                    self.devices[device][key] = value[1:-1] # strip off quotes
+
+    def __getitem__(self, key):
+        return self.devices[key]
+
+    def get(self, key, default=None):
+        return self.devices.get(key, default)
+
+
+class CryptTab(object):
+    """ Dictionary-like interface to crypttab entries with map name keys """
+    def __init__(self, devicetree, blkidTab=None, chroot=""):
+        self.devicetree = devicetree
+        self.blkidTab = blkidTab
+        self.chroot = chroot
+        self.mappings = {}
+
+    def parse(self, chroot=""):
+        """ Parse /etc/crypttab from an existing installation. """
+        if not chroot or not os.path.isdir(chroot):
+            chroot = ""
+
+        path = "%s/etc/crypttab" % chroot
+        log.debug("parsing %s" % path)
+        with open(path) as f:
+            if not self.blkidTab:
+                try:
+                    self.blkidTab = BlkidTab(chroot=chroot)
+                    self.blkidTab.parse()
+                except Exception:
+                    self.blkidTab = None
+
+            for line in f.readlines():
+                (line, pound, comment) = line.partition("#")
+                fields = line.split()
+                if not 2 <= len(fields) <= 4:
+                    continue
+                elif len(fields) == 2:
+                    fields.extend(['none', ''])
+                elif len(fields) == 3:
+                    fields.append('')
+
+                (name, devspec, keyfile, options) = fields
+
+                # resolve devspec to a device in the tree
+                device = self.devicetree.resolveDevice(devspec,
+                                                       blkidTab=self.blkidTab)
+                if device:
+                    self.mappings[name] = {"device": device,
+                                           "keyfile": keyfile,
+                                           "options": options}
+
+    def populate(self):
+        """ Populate the instance based on the device tree's contents. """
+        for device in self.devicetree.devices.values():
+            # XXX should we put them all in there or just the ones that
+            #     are part of a device containing swap or a filesystem?
+            #
+            #       Put them all in here -- we can filter from FSSet
+            if device.format.type != "luks":
+                continue
+
+            key_file = device.format.keyFile
+            if not key_file:
+                key_file = "none"
+
+            options = device.format.options
+            if not options:
+                options = ""
+
+            self.mappings[device.format.mapName] = {"device": device,
+                                                    "keyfile": key_file,
+                                                    "options": options}
+
+    def crypttab(self):
+        """ Write out /etc/crypttab """
+        crypttab = ""
+        for name in self.mappings:
+            entry = self[name]
+            crypttab += "%s UUID=%s %s %s\n" % (name,
+                                                entry['device'].format.uuid,
+                                                entry['keyfile'],
+                                                entry['options'])
+        return crypttab
+
+    def __getitem__(self, key):
+        return self.mappings[key]
+
+    def get(self, key, default=None):
+        return self.mappings.get(key, default)
diff --git a/pkgs/core/pomona/src/storage_old/deviceaction.py b/pkgs/core/pomona/src/storage_old/deviceaction.py
new file mode 100644 (file)
index 0000000..9361b52
--- /dev/null
@@ -0,0 +1,352 @@
+#!/usr/bin/python
+
+import copy
+
+from devices import StorageDevice, PartitionDevice
+from formats import getFormat
+from errors import *
+from udev import *
+
+# The values are just hints as to the ordering.
+# Eg: fsmod and devmod ordering depends on the mod (shrink -v- grow)
+ACTION_TYPE_NONE = 0
+ACTION_TYPE_DESTROY = 1000
+ACTION_TYPE_RESIZE = 500
+ACTION_TYPE_MIGRATE = 250
+ACTION_TYPE_CREATE = 100
+
+action_strings = {ACTION_TYPE_NONE: "None",
+                  ACTION_TYPE_DESTROY: "Destroy",
+                  ACTION_TYPE_RESIZE: "Resize",
+                  ACTION_TYPE_MIGRATE: "Migrate",
+                  ACTION_TYPE_CREATE: "Create"}
+
+ACTION_OBJECT_NONE = 0
+ACTION_OBJECT_FORMAT = 1
+ACTION_OBJECT_DEVICE = 2
+
+object_strings = {ACTION_OBJECT_NONE: "None",
+                  ACTION_OBJECT_FORMAT: "Format",
+                  ACTION_OBJECT_DEVICE: "Device"}
+
+RESIZE_SHRINK = 88
+RESIZE_GROW = 89
+
+resize_strings = {RESIZE_SHRINK: "Shrink",
+                  RESIZE_GROW: "Grow"}
+
+def action_type_from_string(type_string):
+    if type_string is None:
+        return None
+
+    for (k,v) in action_strings.items():
+        if v.lower() == type_string.lower():
+            return k
+
+    return resize_type_from_string(type_string)
+
+def action_object_from_string(type_string):
+    if type_string is None:
+        return None
+
+    for (k,v) in object_strings.items():
+        if v.lower() == type_string.lower():
+            return k
+
+def resize_type_from_string(type_string):
+    if type_string is None:
+        return None
+
+    for (k,v) in resize_strings.items():
+        if v.lower() == type_string.lower():
+            return k
+
+class DeviceAction(object):
+    """ An action that will be carried out in the future on a Device.
+
+        These classes represent actions to be performed on devices or
+        filesystems.
+
+        The operand Device instance will be modified according to the
+        action, but no changes will be made to the underlying device or
+        filesystem until the DeviceAction instance's execute method is
+        called. The DeviceAction instance's cancel method should reverse
+        any modifications made to the Device instance's attributes.
+
+        If the Device instance represents a pre-existing device, the
+        constructor should call any methods or set any attributes that the
+        action will eventually change. Device/DeviceFormat classes should verify
+        that the requested modifications are reasonable and raise an
+        exception if not.
+
+        Only one action of any given type/object pair can exist for any
+        given device at any given time. This is enforced by the
+        DeviceTree.
+
+        Basic usage:
+
+            a = DeviceAction(dev)
+            a.execute()
+
+            OR
+
+            a = DeviceAction(dev)
+            a.cancel()
+
+
+        XXX should we back up the device with a deep copy for forcibly
+            cancelling actions?
+
+            The downside is that we lose any checking or verification that
+            would get done when resetting the Device instance's attributes to
+            their original values.
+
+            The upside is that we would be guaranteed to achieve a total
+            reversal. No chance of, eg: resizes ending up altering Device
+            size due to rounding or other miscalculation.
+"""
+    type = ACTION_TYPE_NONE
+    obj = ACTION_OBJECT_NONE
+
+    def __init__(self, installer, device):
+        self.installer = installer
+        if not isinstance(device, StorageDevice):
+            raise ValueError("arg 1 must be a StorageDevice instance")
+        self.device = device
+
+
+    def execute(self, intf=None):
+        """ perform the action """
+        pass
+
+    def cancel(self):
+        """ cancel the action """
+        pass
+
+    def isDestroy(self):
+        return self.type == ACTION_TYPE_DESTROY
+
+    def isCreate(self):
+        return self.type == ACTION_TYPE_CREATE
+
+    def isMigrate(self):
+        return self.type == ACTION_TYPE_MIGRATE
+
+    def isResize(self):
+        return self.type == ACTION_TYPE_RESIZE
+
+    def isShrink(self):
+        return (self.type == ACTION_TYPE_RESIZE and self.dir == RESIZE_SHRINK)
+
+    def isGrow(self):
+        return (self.type == ACTION_TYPE_RESIZE and self.dir == RESIZE_GROW)
+
+    def isDevice(self):
+        return self.obj == ACTION_OBJECT_DEVICE
+
+    def isFormat(self):
+        return self.obj == ACTION_OBJECT_FORMAT
+
+    def __str__(self):
+        s = "%s %s" % (action_strings[self.type], object_strings[self.obj])
+        if self.isResize():
+            s += " (%s)" % resize_strings[self.dir]
+        if self.isFormat():
+            if self.device.format:
+                fmt_type = self.device.format.type
+            else:
+                fmt_type = None
+            s += " %s on" % fmt_type
+        if self.isMigrate():
+            pass
+        s += " %s (%s)" % (self.device.name, self.device.type)
+        return s
+
+class ActionCreateDevice(DeviceAction):
+    """ Action representing the creation of a new device. """
+    type = ACTION_TYPE_CREATE
+    obj = ACTION_OBJECT_DEVICE
+
+    def __init__(self, installer, device):
+        # FIXME: assert device.fs is None
+        DeviceAction.__init__(self, installer, device)
+
+    def execute(self, intf=None):
+        self.device.create(intf=intf)
+
+
+class ActionDestroyDevice(DeviceAction):
+    """ An action representing the deletion of an existing device. """
+    type = ACTION_TYPE_DESTROY
+    obj = ACTION_OBJECT_DEVICE
+
+    def __init__(self, installer, device):
+        # XXX should we insist that device.fs be None?
+        DeviceAction.__init__(self, installer, device)
+        if device.exists:
+            device.teardown()
+
+    def execute(self, intf=None):
+        self.device.destroy()
+
+
+class ActionResizeDevice(DeviceAction):
+    """ An action representing the resizing of an existing device. """
+    type = ACTION_TYPE_RESIZE
+    obj = ACTION_OBJECT_DEVICE
+
+    def __init__(self, installer, device, newsize):
+        if device.currentSize == newsize:
+            raise ValueError("new size same as old size")
+
+        if not device.resizable:
+            raise ValueError("device is not resizable")
+
+        DeviceAction.__init__(self, installer, device)
+        if newsize > device.currentSize:
+            self.dir = RESIZE_GROW
+        else:
+            self.dir = RESIZE_SHRINK
+        self.origsize = device.targetSize
+        self.device.targetSize = newsize
+
+    def execute(self, intf=None):
+        self.device.resize(intf=intf)
+
+    def cancel(self):
+        self.device.targetSize = self.origsize
+
+
+class ActionCreateFormat(DeviceAction):
+    """ An action representing creation of a new filesystem. """
+    type = ACTION_TYPE_CREATE
+    obj = ACTION_OBJECT_FORMAT
+
+    def __init__(self, installer, device, format=None):
+        DeviceAction.__init__(self, installer, device)
+        if format:
+            self.origFormat = device.format
+            if self.device.format.exists:
+                self.device.format.teardown()
+            self.device.format = format
+        else:
+            self.origFormat = getFormat(None, installer=installer)
+
+    def execute(self, intf=None):
+        if isinstance(self.device, PartitionDevice):
+            if self.format.partedFlag is not None:
+                self.device.setFlag(self.format.partedFlag)
+                self.device.disk.commit()
+
+        udev_settle()
+        self.device.setup()
+        self.device.format.create(intf=intf,
+                                  device=self.device.path,
+                                  options=self.device.formatArgs)
+        # Get the UUID now that the format is created
+        udev_settle()
+        self.device.updateSysfsPath()
+        info = udev_get_block_device("/sys%s" % self.device.sysfsPath)
+        self.device.format.uuid = udev_device_get_uuid(info)
+
+    def cancel(self):
+        self.device.format = self.origFormat
+
+    @property
+    def format(self):
+        return self.device.format
+
+
+class ActionDestroyFormat(DeviceAction):
+    """ An action representing the removal of an existing filesystem.
+
+        XXX this seems unnecessary
+    """
+    type = ACTION_TYPE_DESTROY
+    obj = ACTION_OBJECT_FORMAT
+
+    def __init__(self, installer, device):
+        DeviceAction.__init__(self, installer, device)
+        # Save a deep copy of the device stack this format occupies.
+        # This is necessary since the stack of devices and formats
+        # required to get to this format may get yanked out from under
+        # us between now and execute.
+        self._device = copy.deepcopy(device)
+        self.origFormat = self._device.format
+        if device.format.exists:
+            device.format.teardown()
+        self.device.format = None
+
+    def execute(self, intf=None):
+        """ wipe the filesystem signature from the device """
+        if self.origFormat:
+            if isinstance(self.device, PartitionDevice) and \
+               self.origFormat.partedFlag is not None:
+                # unset partition flags and commit
+                self.device.unsetFlag(self.origFormat.partedFlag)
+                self.device.disk.commit()
+                udev_settle()
+
+            # set up our copy of the original device stack since the
+            # reference we got may have had any number of things changed
+            # since then (most notably, formats removed by this very
+            # class' constructor)
+            self._device.setup()
+            self.origFormat.destroy()
+            udev_settle()
+            self._device.teardown()
+
+    def cancel(self):
+        self.device.format = self.origFormat
+
+    @property
+    def format(self):
+        return self.origFormat
+
+
+class ActionResizeFormat(DeviceAction):
+    """ An action representing the resizing of an existing filesystem.
+
+        XXX Do we even want to support resizing of a filesystem without
+            also resizing the device it resides on?
+    """
+    type = ACTION_TYPE_RESIZE
+    obj = ACTION_OBJECT_FORMAT
+
+    def __init__(self, installer, device, newsize):
+        if device.targetSize == newsize:
+            raise ValueError("new size same as old size")
+
+        DeviceAction.__init__(self, installer, device)
+        if newsize > device.format.currentSize:
+            self.dir = RESIZE_GROW
+        else:
+            self.dir = RESIZE_SHRINK
+        self.origSize = self.device.format.targetSize
+        self.device.format.targetSize = newsize
+
+    def execute(self, intf=None):
+        self.device.setup()
+        self.device.format.doResize(intf=intf)
+
+    def cancel(self):
+        self.device.format.targetSize = self.origSize
+
+class ActionMigrateFormat(DeviceAction):
+    """ An action representing the migration of an existing filesystem. """
+    type = ACTION_TYPE_MIGRATE
+    obj = ACTION_OBJECT_FORMAT
+
+    def __init__(self, installer, device):
+        if not device.format.migratable or not device.format.exists:
+            raise ValueError("device format is not migratable")
+
+        DeviceAction.__init__(self, installer, device)
+        self.device.format.migrate = True
+
+    def execute(self, intf=None):
+        self.device.setup()
+        self.device.format.doMigrate(intf=intf)
+
+    def cancel(self):
+        self.device.format.migrate = False
diff --git a/pkgs/core/pomona/src/storage_old/devicelibs/__init__.py b/pkgs/core/pomona/src/storage_old/devicelibs/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pkgs/core/pomona/src/storage_old/devicelibs/crypto.py b/pkgs/core/pomona/src/storage_old/devicelibs/crypto.py
new file mode 100644 (file)
index 0000000..52c9edd
--- /dev/null
@@ -0,0 +1,109 @@
+#
+# crypto.py
+#
+# Copyright (C) 2009  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): Dave Lehman <dlehman@redhat.com>
+#            Martin Sivak <msivak@redhat.com>
+#
+
+import os
+from pycryptsetup import CryptSetup
+
+from ..errors import *
+
+def askyes(question):
+    return True
+
+def dolog(priority, text):
+    pass
+
+def is_luks(device):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    return cs.isLuks(device)
+
+def luks_uuid(device):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    return cs.luksUUID(device).strip()
+
+def luks_status(name):
+    """True means active, False means inactive (or non-existent)"""
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    return cs.luksStatus(name)!=0
+
+def luks_format(device,
+                passphrase=None, key_file=None,
+                cipher=None, key_size=None):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    key_file_unlink = False
+
+    if passphrase:
+        key_file = cs.prepare_passphrase_file(passphrase)
+        key_file_unlink = True
+    elif key_file and os.path.isfile(key_file):
+        pass
+    else:
+        raise ValueError("luks_format requires either a passphrase or a key file")
+
+    #None is not considered as default value and pycryptsetup doesn't accept it
+    #so we need to filter out all Nones
+    kwargs = {}
+    kwargs["device"] = device
+    if   cipher: kwargs["cipher"]  = cipher
+    if key_file: kwargs["keyfile"] = key_file
+    if key_size: kwargs["keysize"] = key_size
+
+    rc = cs.luksFormat(**kwargs)
+    if key_file_unlink: os.unlink(key_file)
+
+    if rc:
+        raise CryptoError("luks_format failed for '%s'" % device)
+
+def luks_open(device, name, passphrase=None, key_file=None):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    key_file_unlink = False
+
+    if passphrase:
+        key_file = cs.prepare_passphrase_file(passphrase)
+        key_file_unlink = True
+    elif key_file and os.path.isfile(key_file):
+        pass
+    else:
+        raise ValueError("luks_open requires either a passphrase or a key file")
+
+    rc = cs.luksOpen(device = device, name = name, keyfile = key_file)
+    if key_file_unlink: os.unlink(key_file)
+    if rc:
+        raise CryptoError("luks_open failed for %s (%s)" % (device, name))
+
+def luks_close(name):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    rc = cs.luksClose(name)
+    if rc:
+        raise CryptoError("luks_close failed for %s" % name)
+
+def luks_add_key(device,
+                 new_passphrase=None, new_key_file=None,
+                 passphrase=None, key_file=None):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    return cs.addKey(device, new_passphrase, new_key_file, passphrase, key_file)
+
+
+def luks_remove_key(device,
+                    del_passphrase=None, del_key_file=None,
+                    passphrase=None, key_file=None):
+    cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+    return cs.removeKey(device, del_passphrase, del_key_file, passphrase, key_file)
diff --git a/pkgs/core/pomona/src/storage_old/devicelibs/lvm.py b/pkgs/core/pomona/src/storage_old/devicelibs/lvm.py
new file mode 100644 (file)
index 0000000..20fa02a
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+
+import os
+import re
+
+import util
+
+MAX_LV_SLOTS = 256
+
+def has_lvm():
+    has_lvm = False
+    for path in os.environ["PATH"].split(":"):
+        if os.access("%s/lvm" % path, os.X_OK):
+            has_lvm = True
+            break
+
+    if has_lvm:
+        has_lvm = False
+        for line in open("/proc/devices").readlines():
+            if "device-mapper" in line.split():
+                has_lvm = True
+                break
+
+    return has_lvm
+
+# Start config_args handling code
+#
+# Theoretically we can handle all that can be handled with the LVM --config
+# argument.  For every time we call an lvm_cc (lvm compose config) funciton
+# we regenerate the config_args with all global info.
+config_args = [] # Holds the final argument list
+config_args_data = { "filterRejects": [],    # regular expressions to reject.
+                            "filterAccepts": [] }   # regexp to accept
+
+def _composeConfig():
+    """lvm command accepts lvm.conf type arguments preceded by --config. """
+    global config_args, config_args_data
+    config_args = []
+
+    filter_string = ""
+    rejects = config_args_data["filterRejects"]
+    # we don't need the accept for now.
+    # accepts = config_args_data["filterAccepts"]
+    # if len(accepts) > 0:
+    #   for i in range(len(rejects)):
+    #       filter_string = filter_string + ("\"a|%s|\", " % accpets[i])
+
+    if len(rejects) > 0:
+        for i in range(len(rejects)):
+            filter_string = filter_string + ("\"r|%s|\"," % rejects[i])
+
+    filter_string = " filter=[%s] " % filter_string.strip(",")
+
+    # As we add config strings we should check them all.
+    if filter_string == "":
+        # Nothing was really done.
+        return
+
+    # devices_string can have (inside the brackets) "dir", "scan",
+    # "preferred_names", "filter", "cache_dir", "write_cache_state",
+    # "types", "sysfs_scan", "md_component_detection".  see man lvm.conf.
+    devices_string = " devices {%s} " % (filter_string) # strings can be added
+    config_string = devices_string # more strings can be added.
+    config_args = ["--config", config_string]
+
+def lvm_cc_addFilterRejectRegexp(regexp):
+    """ Add a regular expression to the --config string."""
+    global config_args_data
+    config_args_data["filterRejects"].append(regexp)
+
+    # compoes config once more.
+    _composeConfig()
+
+def lvm_cc_resetFilter():
+    global config_args_data
+    config_args_data["filterRejects"] = []
+    config_args_data["filterAccepts"] = []
+# End config_args handling code.
+
+# Names that should not be used int the creation of VGs
+lvm_vg_blacklist = []
+def blacklistVG(name):
+    global lvm_vg_blacklist
+    lvm_vg_blacklist.append(name)
+
+def getPossiblePhysicalExtents(floor=0):
+    """Returns a list of integers representing the possible values for
+       the physical extent of a volume group.  Value is in KB.
+
+       floor - size (in KB) of smallest PE we care about.
+    """
+
+    possiblePE = []
+    curpe = 8
+    while curpe <= 16384*1024:
+        if curpe >= floor:
+            possiblePE.append(curpe)
+        curpe = curpe * 2
+
+    return possiblePE
+
+def getMaxLVSize():
+    """ Return the maximum size (in MB) of a logical volume. """
+    if util.getArch() in ("x86_64",): #64bit architectures
+        return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..)
+    else:
+        return (16*1024*1024) #Max is 16TiB
+
+def safeLvmName(name):
+    tmp = name.strip()
+    tmp = tmp.replace("/", "_")
+    tmp = re.sub("[^0-9a-zA-Z._]", "", tmp)
+    tmp = tmp.lstrip("_")
+
+    return tmp
diff --git a/pkgs/core/pomona/src/storage_old/devicelibs/swap.py b/pkgs/core/pomona/src/storage_old/devicelibs/swap.py
new file mode 100644 (file)
index 0000000..57a4065
--- /dev/null
@@ -0,0 +1,85 @@
+import resource
+
+import util
+import os
+
+from ..errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+def mkswap(device, label=''):
+    argv = []
+    if label:
+        argv.extend(["-L", label])
+    argv.append(device)
+
+    rc = util.execWithRedirect("mkswap", argv,
+                                stderr = "/dev/tty5",
+                                stdout = "/dev/tty5",
+                                searchPath=1)
+
+    if rc:
+        raise SwapError("mkswap failed for '%s'" % device)
+
+def swapon(device, priority=None):
+    pagesize = resource.getpagesize()
+    buf = None
+    if pagesize > 2048:
+        num = pagesize
+    else:
+        num = 2048
+
+    try:
+        fd = os.open(device, os.O_RDONLY)
+        buf = os.read(fd, num)
+    except OSError:
+        pass
+    finally:
+        try:
+            os.close(fd)
+        except (OSError, UnboundLocalError):
+            pass
+
+    if buf is not None and len(buf) == pagesize:
+        sig = buf[pagesize - 10:]
+        if sig == 'SWAP-SPACE':
+            raise OldSwapError
+        if sig == 'S1SUSPEND\x00' or sig == 'S2SUSPEND\x00':
+            raise SuspendError
+
+    argv = []
+    if isinstance(priority, int) and 0 <= priority <= 32767:
+        argv.extend(["-p", "%d" % priority])
+    argv.append(device)
+
+    rc = util.execWithRedirect("swapon",
+                                argv,
+                                stderr = "/dev/tty5",
+                                stdout = "/dev/tty5",
+                                searchPath=1)
+
+    if rc:
+        raise SwapError("swapon failed for '%s'" % device)
+
+def swapoff(device):
+    rc = util.execWithRedirect("swapoff", [device],
+                                stderr = "/dev/tty5",
+                                stdout = "/dev/tty5",
+                                searchPath=1)
+
+    if rc:
+        raise SwapError("swapoff failed for '%s'" % device)
+
+def swapstatus(device):
+    lines = open("/proc/swaps").readlines()
+    status = False
+    for line in lines:
+        if not line.strip():
+            continue
+
+        if line.split()[0] == device:
+            status = True
+            break
+
+    return status
diff --git a/pkgs/core/pomona/src/storage_old/devices.py b/pkgs/core/pomona/src/storage_old/devices.py
new file mode 100644 (file)
index 0000000..e1ba7de
--- /dev/null
@@ -0,0 +1,1698 @@
+#/usr/bin/python
+
+import copy
+import math
+import parted
+import _ped
+
+from errors import *
+from formats import get_device_format_class, getFormat
+from udev import *
+from util import notify_kernel, numeric_type
+
+def devicePathToName(devicePath):
+    if devicePath.startswith("/dev/"):
+        name = devicePath[5:]
+    else:
+        name = devicePath
+
+    if name.startswith("mapper/"):
+        name = name[7:]
+
+    return name
+
+class Device(object):
+    """ A generic device.
+
+        Device instances know which devices they depend upon (parents
+        attribute). They do not know which devices depend upon them, but
+        they do know whether or not they have any dependent devices
+        (isleaf attribute).
+
+        A Device's setup method should set up all parent devices as well
+        as the device itself. It should not run the resident format's
+        setup method.
+
+            Which Device types rely on their parents' formats being active?
+                DMCryptDevice
+
+        A Device's teardown method should accept the keyword argument
+        recursive, which takes a boolean value and indicates whether or
+        not to recursively close parent devices.
+
+        A Device's create method should create all parent devices as well
+        as the device itself. It should also run the Device's setup method
+        after creating the device. The create method should not create a
+        device's resident format.
+
+            Which device type rely on their parents' formats to be created
+            before they can be created/assembled?
+                VolumeGroup
+                DMCryptDevice
+
+        A Device's destroy method should destroy any resident format
+        before destroying the device itself.
+
+    """
+    _type = "generic device"
+
+    def __init__(self, installer, name, parents=None, description=''):
+        """ Create a Device instance.
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+
+            Keyword Arguments:
+
+                parents -- a list of required Device instances
+                description -- a string describing the device
+
+        """
+        self.installer = installer
+
+        self._name = name
+        if parents is None:
+            parents = []
+        elif not isinstance(parents, list):
+            raise ValueError("parents must be a list of Device instances")
+        self.parents = parents
+        self.kids = 0
+        self.description = description
+
+        for parent in self.parents:
+            parent.addChild()
+
+    def __deepcopy__(self, memo):
+        """ Create a deep copy of a Device instance.
+
+            We can't do copy.deepcopy on parted objects, which is okay.
+            For these parted objects, we just do a shallow copy.
+        """
+        new = self.__class__.__new__(self.__class__)
+        memo[id(self)] = new
+        shallow_copy_attrs = ('partedDisk', '_partedDevice',
+                             '_partedPartition', '_origPartedDisk',
+                             '_raidSet', 'installer', 'screen')
+        for (attr, value) in self.__dict__.items():
+            if attr in shallow_copy_attrs:
+                setattr(new, attr, copy.copy(value))
+            else:
+                setattr(new, attr, copy.deepcopy(value, memo))
+
+        return new
+
+    def removeChild(self):
+        self.kids -= 1
+
+    def addChild(self):
+        self.kids += 1
+
+    def setup(self, intf=None):
+        """ Open, or set up, a device. """
+        raise NotImplementedError("setup method not defined for Device")
+
+    def teardown(self, recursive=None):
+        """ Close, or tear down, a device. """
+        raise NotImplementedError("teardown method not defined for Device")
+
+    def create(self, intf=None):
+        """ Create the device. """
+        raise NotImplementedError("create method not defined for Device")
+
+    def destroy(self):
+        """ Destroy the device. """
+        raise NotImplementedError("destroy method not defined for Device")
+
+    def setupParents(self):
+        """ Run setup method of all parent devices. """
+        for parent in self.parents:
+            parent.setup()
+
+    def teardownParents(self, recursive=None):
+        """ Run teardown method of all parent devices. """
+        for parent in self.parents:
+            parent.teardown(recursive=recursive)
+
+    def createParents(self):
+        """ Run create method of all parent devices. """
+        self.installer.log.info("NOTE: recursive device creation disabled")
+        for parent in self.parents:
+            if not parent.exists:
+                raise DeviceError("parent device does not exist")
+            #parent.create()
+
+    def dependsOn(self, dep):
+        """ Return True if this device depends on dep. """
+        # XXX does a device depend on itself?
+        if dep in self.parents:
+            return True
+
+        for parent in self.parents:
+            if parent.dependsOn(dep):
+                return True
+
+        return False
+
+    @property
+    def status(self):
+        """ This device's status.
+
+            For now, this should return a boolean:
+                True    the device is open and ready for use
+                False   the device is not open
+        """
+        return False
+
+    @property
+    def name(self):
+        """ This device's name. """
+        return self._name
+
+    @property
+    def isleaf(self):
+        """ True if this device has no children. """
+        return self.kids == 0
+
+    @property
+    def typeDescription(self):
+        """ String describing the device type. """
+        return self._type
+
+    @property
+    def type(self):
+        """ Device type. """
+        return self._type
+
+    @property
+    def mediaPresent(self):
+        return True
+
+
+class StorageDevice(Device):
+    """ A generic storage device.
+
+        A fully qualified path to the device node can be obtained via the
+        path attribute, although it is not guaranteed to be useful, or
+        even present, unless the StorageDevice's setup method has been
+        run.
+
+        StorageDevice instances can optionally contain a filesystem,
+        represented by an FS instance. A StorageDevice's create method
+        should create a filesystem if one has been specified.
+    """
+    _type = "storage device"
+    _devDir = "/dev"
+    sysfsBlockDir = "class/block"
+    _resizable = False
+
+    def __init__(self, installer, device, format=None,
+                 size=None, major=None, minor=None,
+                 sysfsPath='', parents=None, exists=None):
+        """ Create a StorageDevice instance.
+
+            Arguments:
+
+                device -- the device name (generally a device node's basename)
+
+            Keyword Arguments:
+
+                size -- the device's size (units/format TBD)
+                major -- the device major
+                minor -- the device minor
+                sysfsPath -- sysfs device path
+                format -- a DeviceFormat instance
+                parents -- a list of required Device instances
+                description -- a string describing the device
+
+        """
+        self.installer = installer
+
+        # allow specification of individual parents
+        if isinstance(parents, Device):
+            parents = [parents]
+
+        Device.__init__(self, installer, device, parents=parents)
+
+        self.uuid = None
+        self._format = None
+        self._size = numeric_type(size)
+        self.major = numeric_type(major)
+        self.minor = numeric_type(minor)
+        self.sysfsPath = sysfsPath
+        self.exists = exists
+
+        # this may be handy for disk, dmraid, mpath, mdraid
+        self.diskLabel = None
+
+        self.format = format
+        self.fstabComment = ""
+        self._targetSize = self._size
+
+        self._partedDevice = None
+
+    @property
+    def partedDevice(self):
+        if self.exists and self.status and not self._partedDevice:
+            # We aren't guaranteed to be able to get a device.  In
+            # particular, built-in USB flash readers show up as devices but
+            # do not always have any media present, so parted won't be able
+            # to find a device.
+            try:
+                self._partedDevice = parted.Device(path=self.path)
+            except _ped.DeviceException:
+                pass
+
+        return self._partedDevice
+
+    def _getTargetSize(self):
+        return self._targetSize
+
+    def _setTargetSize(self, newsize):
+        self._targetSize = newsize
+
+    targetSize = property(lambda s: s._getTargetSize(),
+                          lambda s, v: s._setTargetSize(v),
+                          doc="Target size of this device")
+
+    @property
+    def path(self):
+        """ Device node representing this device. """
+        return "%s/%s" % (self._devDir, self.name)
+
+    def updateSysfsPath(self):
+        """ Update this device's sysfs path. """
+        sysfsName = self.name.replace("/", "!")
+        path = os.path.join("/sys", self.sysfsBlockDir, sysfsName)
+        self.sysfsPath = os.path.realpath(path)[4:]
+        self.installer.log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath))
+
+    @property
+    def formatArgs(self):
+        """ Device-specific arguments to format creation program. """
+        return []
+
+    @property
+    def resizable(self):
+        """ Can this type of device be resized? """
+        return self._resizable and self.exists
+
+    def notifyKernel(self):
+        """ Send a 'change' uevent to the kernel for this device. """
+        if not self.exists:
+            self.installer.log.debug("Not sending change uevent for non-existent device")
+            return
+
+        if not self.status:
+            self.installer.log.debug("Not sending change uevent for inactive device")
+            return
+
+        path = os.path.normpath("/sys/%s" % self.sysfsPath)
+        try:
+            notify_kernel(path, action="change")
+        except Exception, e:
+            self.installer.log.warning("Failed to notify kernel of change: %s" % e)
+
+    @property
+    def fstabSpec(self):
+        spec = self.path
+        if self.format and self.format.uuid:
+            spec = "UUID=%s" % self.format.uuid
+        return spec
+
+    def resize(self, intf=None):
+        """ Resize the device.
+
+            New size should already be set.
+        """
+        raise NotImplementedError("resize method not defined for StorageDevice")
+
+    def setup(self, intf=None):
+        """ Open, or set up, a device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        self.setupParents()
+        for parent in self.parents:
+            parent.format.setup()
+
+    def teardown(self, recursive=None):
+        """ Close, or tear down, a device. """
+        if not self.exists and not recursive:
+            raise DeviceError("device has not been created")
+
+        if self.status and self.format.exists:
+            self.format.teardown()
+            udev_settle(timeout=10)
+
+        if recursive:
+            self.teardownParents(recursive=recursive)
+
+    def _getSize(self):
+        """ Get the device's size in MB, accounting for pending changes. """
+        if self.exists and not self.mediaPresent:
+            return 0
+
+        if self.exists and self.partedDevice:
+            self._size = self.currentSize
+
+        size = self._size
+        if self.exists and self.resizable and self.targetSize != size:
+            size = self.targetSize
+
+        return size
+
+    def _setSize(self, newsize):
+        """ Set the device's size to a new value. """
+        if newsize > self.maxSize:
+            raise DeviceError("device cannot be larger than %s MB" %
+                              (self.maxSize(),))
+        self._size = newsize
+
+    size = property(lambda x: x._getSize(),
+                    lambda x, y: x._setSize(y),
+                    doc="The device's size in MB, accounting for pending changes")
+
+    @property
+    def currentSize(self):
+        """ The device's actual size. """
+        size = 0
+        if self.exists and self.partedDevice:
+            size = self.partedDevice.getSize()
+        elif self.exists:
+            size = self._size
+        return size
+
+    @property
+    def minSize(self):
+        """ The minimum size this device can be. """
+        if self.exists:
+            self.setup()
+
+        if self.format.minSize:
+            return self.format.minSize
+        else:
+            return self.size
+
+    @property
+    def maxSize(self):
+        """ The maximum size this device can be. """
+        if self.format.maxSize > self.currentSize:
+            return self.currentSize
+        else:
+            return self.format.maxSize
+
+    @property
+    def status(self):
+        """ This device's status.
+
+            For now, this should return a boolean:
+                True    the device is open and ready for use
+                False   the device is not open
+        """
+        if not self.exists:
+            return False
+        return os.access(self.path, os.W_OK)
+
+    def _setFormat(self, format):
+        """ Set the Device's format. """
+        if not format:
+            format = getFormat(None, installer=self.installer, device=self.path, exists=self.exists)
+        if self._format and self._format.status:
+            # FIXME: self.format.status doesn't mean much
+            raise DeviceError("Cannot replace active format")
+
+        self._format = format
+
+    def _getFormat(self):
+        return self._format
+
+    format = property(lambda d: d._getFormat(),
+                      lambda d,f: d._setFormat(f),
+                      doc="The device's formatting.")
+
+    def create(self, intf=None):
+        """ Create the device. """
+        if self.exists:
+            raise DeviceError("device has already been created")
+
+        self.createParents()
+        self.setupParents()
+        self.exists = True
+        self.setup()
+
+    def destroy(self):
+        """ Destroy the device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        if not self.isleaf:
+            raise DeviceError("Cannot destroy non-leaf device")
+
+        self.exists = False
+
+    @property
+    def removable(self):
+        devpath = os.path.normpath("/sys/%s" % self.sysfsPath)
+        remfile = os.path.normpath("%s/removable" % devpath)
+        return (self.sysfsPath and os.path.exists(devpath) and
+                os.access(remfile, os.R_OK) and
+                open(remfile).readline().strip() == "1")
+
+class DiskDevice(StorageDevice):
+    """ A disk """
+    _type = "disk"
+
+    def __init__(self, installer, device, format=None,
+                 size=None, major=None, minor=None, sysfsPath='', \
+                 parents=None, initcb=None, initlabel=None):
+        """ Create a DiskDevice instance.
+
+            Arguments:
+
+                device -- the device name (generally a device node's basename)
+
+            Keyword Arguments:
+
+                size -- the device's size (units/format TBD)
+                major -- the device major
+                minor -- the device minor
+                sysfsPath -- sysfs device path
+                format -- a DeviceFormat instance
+                parents -- a list of required Device instances
+                removable -- whether or not this is a removable device
+
+                initcb -- the call back to be used when initiating disk.
+                initlabel -- whether to start with a fresh disklabel
+
+
+            DiskDevices always exist.
+        """
+        self.installer = installer
+        StorageDevice.__init__(self, self.installer, device, format=format,
+                                size=size, major=major, minor=minor, exists=True,
+                               sysfsPath=sysfsPath, parents=parents)
+
+        self.partedDisk = None
+
+        if self.partedDevice:
+            if initlabel:
+                self.partedDisk = self.freshPartedDisk()
+            else:
+                try:
+                    self.partedDisk = parted.Disk(device=self.partedDevice)
+                except _ped.DiskLabelException:
+                    # if we have a cb function use it. else an error.
+                    if initcb is not None and initcb():
+                        self.partedDisk = parted.freshDisk(device=self.partedDevice, \
+                                ty = platform.getPlatform(None).diskType)
+                    else:
+                        raise DeviceUserDeniedFormatError("User prefered to not format.")
+
+        # We save the actual state of the disk here. Before the first
+        # modification (addPartition or removePartition) to the partition
+        # table we reset self.partedPartition to this state so we can
+        # perform the modifications one at a time.
+        if self.partedDisk:
+            self._origPartedDisk = self.partedDisk.duplicate()
+        else:
+            self._origPartedDisk = None
+
+    def freshPartedDisk(self):
+        labelType = platform.getPlatform(None).diskType
+        return parted.freshDisk(device=self.partedDevice, ty=labelType)
+
+    @property
+    def mediaPresent(self):
+        return self.partedDevice is not None
+
+    @property
+    def model(self):
+        return getattr(self.partedDevice, "model", None)
+
+    @property
+    def size(self):
+        """ The disk's size in MB """
+        return super(DiskDevice, self).size
+    #size = property(StorageDevice._getSize)
+
+    def resetPartedDisk(self):
+        """ Reset parted.Disk to reflect the actual layout of the disk. """
+        self.partedDisk = self._origPartedDisk
+
+    def removePartition(self, device):
+        if not self.mediaPresent:
+            raise DeviceError("Cannot remove partition from disk %s which has no media" % self.name)
+
+        partition = self.partedDisk.getPartitionByPath(device.path)
+        if partition:
+            self.partedDisk.removePartition(partition)
+
+    def addPartition(self, device):
+        if not self.mediaPresent:
+            raise DeviceError("cannot add partition to disk %s which has no media" % self.name)
+
+        for part in self.partedDisk.partitions:
+            self.installer.log.debug("Disk %s: partition %s has geom %s" % (self.name,
+                                                             part.getDeviceNodeName(),
+                                                             part.geometry))
+
+        geometry = device.partedPartition.geometry
+        constraint = parted.Constraint(exactGeom=geometry)
+        partition = parted.Partition(disk=self.partedDisk,
+                                     type=device.partedPartition.type,
+                                     geometry=geometry)
+        self.partedDisk.addPartition(partition, constraint=constraint)
+
+    def probe(self):
+        """ Probe for any missing information about this device.
+
+            pyparted should be able to tell us anything we want to know.
+            size, disklabel type, maybe even partition layout
+        """
+        if not self.diskLabel:
+            self.installer.log.debug("Setting %s diskLabel to %s" % (self.name,
+                                                      self.partedDisk.type))
+            self.diskLabel = self.partedDisk.type
+
+    def commit(self, intf=None):
+        """ Commit changes to the device. """
+        if not self.mediaPresent:
+            raise DeviceError("cannot commit to disk %s which has no media" % self.name)
+
+        self.setupParents()
+        self.setup()
+
+        # give committing 5 tries, failing that, raise an exception
+        attempt = 1
+        maxTries = 5
+        keepTrying = True
+
+        while keepTrying and (attempt <= maxTries):
+            try:
+                self.partedDisk.commit()
+                keepTrying = False
+            except parted.DiskException as msg:
+                self.installer.log.warning(msg)
+                attempt += 1
+
+        if keepTrying:
+            raise DeviceError("cannot commit to disk %s after %d attempts" % (self.name, maxTries,))
+
+        # commit makes the kernel re-scan the partition table
+        udev_settle()
+
+    def destroy(self):
+        """ Destroy the device. """
+        if not self.mediaPresent:
+            raise DeviceError("cannot destroy disk %s which has no media" % self.name)
+
+        self.partedDisk.deleteAllPartitions()
+        # this is perhaps a separate operation (wiping the disklabel)
+        self.partedDisk.clobber()
+        self.partedDisk.commit()
+        self.teardown()
+
+    def setup(self, intf=None):
+        """ Open, or set up, a device. """
+        if not os.path.exists(self.path):
+            raise DeviceError("device does not exist")
+
+class PartitionDevice(StorageDevice):
+    """ A disk partition.
+
+        On types and flags...
+
+        We don't need to deal with numerical partition types at all. The
+        only type we are concerned with is primary/logical/extended. Usage
+        specification is accomplished through the use of flags, which we
+        will set according to the partition's format.
+    """
+    _type = "partition"
+    _resizable = True
+
+    def __init__(self, installer, name, format=None,
+                 size=None, grow=False, maxsize=None,
+                 major=None, minor=None, bootable=None,
+                 sysfsPath='', parents=None, exists=None,
+                 partType=None, primary=False, weight=0):
+        """ Create a PartitionDevice instance.
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+
+            Keyword Arguments:
+
+                exists -- indicates whether this is an existing device
+                format -- the device's format (DeviceFormat instance)
+
+                For existing partitions:
+
+                    parents -- the disk that contains this partition
+                    major -- the device major
+                    minor -- the device minor
+                    sysfsPath -- sysfs device path
+
+                For new partitions:
+
+                    partType -- primary,extended,&c (as parted constant)
+                    grow -- whether or not to grow the partition
+                    maxsize -- max size for growable partitions (in MB)
+                    size -- the device's size (in MB)
+                    bootable -- whether the partition is bootable
+                    parents -- a list of potential containing disks
+                    weight -- an initial sorting weight to assign
+        """
+        self.req_disks = []
+        self.req_partType = None
+        self.req_primary = None
+        self.req_grow = None
+        self.req_bootable = None
+        self.req_size = 0
+        self.req_base_size = 0
+        self.req_max_size = 0
+        self.req_base_weight = 0
+
+        self._bootable = False
+
+        StorageDevice.__init__(self, installer, name, format=format, size=size,
+                               major=major, minor=minor, exists=exists,
+                               sysfsPath=sysfsPath, parents=parents)
+
+        if not exists:
+            # this is a request, not a partition -- it has no parents
+            self.req_disks = self.parents[:]
+            for dev in self.parents:
+                dev.removeChild()
+            self.parents = []
+
+        # FIXME: Validate partType, but only if this is a new partition
+        #        Otherwise, overwrite it with the partition's type.
+        self._partType = None
+        self.partedFlags = {}
+        self._partedPartition = None
+
+        # FIXME: Validate size, but only if this is a new partition.
+        #        For existing partitions we will get the size from
+        #        parted.
+
+        if self.exists:
+            #self.partedPartition = parted.getPartitionByName(self.path)
+            self._partedPartition = self.disk.partedDisk.getPartitionByPath(self.path)
+            if not self._partedPartition:
+                raise DeviceError("cannot find parted partition instance")
+
+            # collect information about the partition from parted
+            self.probe()
+        else:
+            # XXX It might be worthwhile to create a shit-simple
+            #     PartitionRequest class and pass one to this constructor
+            #     for new partitions.
+            self.req_name = name
+            self.req_partType = partType
+            self.req_primary = primary
+            self.req_max_size = numeric_type(maxsize)
+            self.req_grow = grow
+            self.req_bootable = bootable
+
+            # req_size may be manipulated in the course of partitioning
+            self.req_size = self._size
+
+            # req_base_size will always remain constant
+            self.req_base_size = self._size
+
+            self.req_base_weight = weight
+
+    def _setTargetSize(self, newsize):
+        if newsize != self.currentSize:
+            # change this partition's geometry in-memory so that other
+            # partitioning operations can complete (e.g., autopart)
+            self._targetSize = newsize
+            disk = self.disk.partedDisk
+
+            # resize the partition's geometry in memory
+            (constraint, geometry) = self._computeResize(self.partedPartition)
+            disk.setPartitionGeometry(partition=self.partedPartition,
+                                      constraint=constraint,
+                                      start=geometry.start, end=geometry.end)
+
+    @property
+    def path(self):
+        """ Device node representing this device. """
+        if not self.parents:
+            # Bogus, but code in various places compares devices by path
+            # So we must return something unique
+            return self.name
+
+        return "%s/%s" % (self.parents[0]._devDir, self.name)
+
+    @property
+    def partType(self):
+        """ Get the partition's type (as parted constant). """
+        try:
+            ptype = self.partedPartition.type
+        except AttributeError:
+            ptype = self._partType
+
+        if not self.exists and ptype is None:
+            ptype = self.req_partType
+
+        return ptype
+
+    @property
+    def isExtended(self):
+        return self.partType & parted.PARTITION_EXTENDED
+
+    @property
+    def isLogical(self):
+        return self.partType & parted.PARTITION_LOGICAL
+
+    @property
+    def isPrimary(self):
+        return self.partType == parted.PARTITION_NORMAL
+
+    @property
+    def isProtected(self):
+        return self.partType & parted.PARTITION_PROTECTED
+
+    def _getPartedPartition(self):
+        return self._partedPartition
+
+    def _setPartedPartition(self, partition):
+        """ Set this PartitionDevice's parted Partition instance. """
+        if partition is None:
+            path = None
+        elif isinstance(partition, parted.Partition):
+            path = partition.path
+        else:
+            raise ValueError("partition must be a parted.Partition instance")
+
+        self._partedPartition = partition
+        self.updateName()
+
+    partedPartition = property(lambda d: d._getPartedPartition(),
+                               lambda d,p: d._setPartedPartition(p))
+
+    def _getWeight(self):
+        return self.req_base_weight
+
+    def _setWeight(self, weight):
+        self.req_base_weight = weight
+
+    weight = property(lambda d: d._getWeight(),
+                      lambda d,w: d._setWeight(w))
+
+    def updateSysfsPath(self):
+        """ Update this device's sysfs path. """
+        if not self.parents:
+            self.sysfsPath = ''
+
+        elif self.parents[0]._devDir == "/dev/mapper":
+            dm_node = dm.dm_node_from_name(self.name)
+            path = os.path.join("/sys", self.sysfsBlockDir, dm_node)
+            self.sysfsPath = os.path.realpath(path)[4:]
+
+        else:
+            StorageDevice.updateSysfsPath(self)
+
+    def updateName(self):
+        if self.partedPartition is None:
+            self._name = self.req_name
+        else:
+            self._name = \
+                devicePathToName(self.partedPartition.getDeviceNodeName())
+
+    def dependsOn(self, dep):
+        """ Return True if this device depends on dep. """
+        if isinstance(dep, PartitionDevice) and dep.isExtended and self.isLogical:
+            return True
+
+        return Device.dependsOn(self, dep)
+
+    def _setFormat(self, format):
+        """ Set the Device's format. """
+        StorageDevice._setFormat(self, format)
+
+    def _setBootable(self, bootable):
+        """ Set the bootable flag for this partition. """
+        if self.partedPartition:
+            if self.flagAvailable(parted.PARTITION_BOOT):
+                if bootable:
+                    self.setFlag(parted.PARTITION_BOOT)
+                else:
+                    self.unsetFlag(parted.PARTITION_BOOT)
+            else:
+                raise DeviceError(_("boot flag not available for this "
+                                    "partition"))
+
+            self._bootable = bootable
+        else:
+            self.req_bootable = bootable
+
+    def _getBootable(self):
+        return self._bootable or self.req_bootable
+
+    bootable = property(_getBootable, _setBootable)
+
+    def flagAvailable(self, flag):
+        if not self.partedPartition:
+            return
+
+        return self.partedPartition.isFlagAvailable(flag)
+
+    def getFlag(self, flag):
+        if not self.partedPartition or not self.flagAvailable(flag):
+            return
+
+        return self.partedPartition.getFlag(flag)
+
+    def setFlag(self, flag):
+        if not self.partedPartition or not self.flagAvailable(flag):
+            return
+
+        self.partedPartition.setFlag(flag)
+
+    def unsetFlag(self, flag):
+        if not self.partedPartition or not self.flagAvailable(flag):
+            return
+
+        self.partedPartition.unsetFlag(flag)
+
+    def probe(self):
+        """ Probe for any missing information about this device.
+
+            size, partition type, flags
+        """
+        if not self.exists:
+            return
+
+        # this is in MB
+        self._size = self.partedPartition.getSize()
+        self.targetSize = self._size
+
+        self._partType = self.partedPartition.type
+
+        self._bootable = self.getFlag(parted.PARTITION_BOOT)
+
+    def create(self, intf=None):
+        """ Create the device. """
+        if self.exists:
+            raise DeviceError("device already exists")
+
+        self.createParents()
+        self.setupParents()
+
+        self.disk.addPartition(self)
+        self.disk.commit()
+
+        self.partedPartition = self.disk.partedDisk.getPartitionByPath(self.path)
+
+        self.exists = True
+        self.setup()
+
+    def _computeResize(self, partition):
+        # compute new size for partition
+        currentGeom = partition.geometry
+        currentDev = currentGeom.device
+        newLen = long(self.targetSize * 1024 * 1024) / currentDev.sectorSize
+        newGeometry = parted.Geometry(device=currentDev,
+                                      start=currentGeom.start,
+                                      length=newLen)
+        constraint = parted.Constraint(exactGeom=newGeometry)
+
+        return (constraint, newGeometry)
+
+    def resize(self, intf=None):
+        """ Resize the device.
+
+            self.targetSize must be set to the new size.
+        """
+        if self.targetSize != self.currentSize:
+            # partedDisk has been restored to _origPartedDisk, so
+            # recalculate resize geometry because we may have new
+            # partitions on the disk, which could change constraints
+            partition = self.disk.partedDisk.getPartitionByPath(self.path)
+            (constraint, geometry) = self._computeResize(partition)
+
+            self.disk.partedDisk.setPartitionGeometry(partition=partition,
+                                                      constraint=constraint,
+                                                      start=geometry.start,
+                                                      end=geometry.end)
+
+            self.disk.commit()
+            self.notifyKernel()
+
+    def destroy(self):
+        """ Destroy the device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        if not self.sysfsPath:
+            return
+
+        if not self.isleaf:
+            raise DeviceError("Cannot destroy non-leaf device")
+
+        self.setupParents()
+        self.disk.removePartition(self)
+        self.disk.commit()
+
+        self.exists = False
+
+    def _getSize(self):
+        """ Get the device's size. """
+        size = self._size
+        if self.partedPartition:
+            # this defaults to MB
+            size = self.partedPartition.getSize()
+        return size
+
+    def _setSize(self, newsize):
+        """ Set the device's size (for resize, not creation).
+
+            Arguments:
+
+                newsize -- the new size (in MB)
+
+        """
+        if not self.exists:
+            raise DeviceError("device does not exist")
+
+        if newsize > self.disk.size:
+            raise ValueError("partition size would exceed disk size")
+
+        # this defaults to MB
+        maxAvailableSize = self.partedPartition.getMaxAvailableSize()
+
+        if newsize > maxAvailableSize:
+            raise ValueError("new size is greater than available space")
+
+         # now convert the size to sectors and update the geometry
+        geometry = self.partedPartition.geometry
+        physicalSectorSize = geometry.device.physicalSectorSize
+
+        new_length = (newsize * (1024 * 1024)) / physicalSectorSize
+        geometry.length = new_length
+
+    def _getDisk(self):
+        """ The disk that contains this partition."""
+        try:
+            disk = self.parents[0]
+        except IndexError:
+            disk = None
+        return disk
+
+    def _setDisk(self, disk):
+        """Change the parent.
+
+        Setting up a disk is not trivial.  It has the potential to change
+        the underlying object.  If necessary we must also change this object.
+        """
+        if self.disk:
+            self.disk.removeChild()
+
+        if disk:
+            self.parents = [disk]
+            disk.addChild()
+        else:
+            self.parents = []
+
+    disk = property(lambda p: p._getDisk(), lambda p,d: p._setDisk(d))
+
+    @property
+    def maxSize(self):
+        """ The maximum size this partition can be. """
+        # XXX: this is MB by default
+        maxPartSize = self.partedPartition.getMaxAvailableSize()
+
+        if self.format.maxSize > maxPartSize:
+            return maxPartSize
+        else:
+            return self.format.maxSize
+
+class OpticalDevice(StorageDevice):
+    """ An optical drive, eg: cdrom, dvd+r, &c.
+
+        XXX Is this useful?
+    """
+    _type = "cdrom"
+
+    def __init__(self, installer, name, major=None, minor=None, exists=None,
+                 format=None, parents=None, sysfsPath=''):
+        StorageDevice.__init__(self, installer, name, format=format,
+                               major=major, minor=minor, exists=True,
+                               parents=parents, sysfsPath=sysfsPath)
+
+    @property
+    def mediaPresent(self):
+        """ Return a boolean indicating whether or not the device contains
+            media.
+        """
+        if not self.exists:
+            raise DeviceError("device has not been created", self.path)
+
+        try:
+            fd = os.open(self.path, os.O_RDONLY)
+        except OSError as e:
+            # errno 123 = No medium found
+            if e.errno == 123:
+                return False
+            else:
+                return True
+        else:
+            os.close(fd)
+            return True
+
+    def eject(self):
+        """ Eject the drawer. """
+        #import _isys
+
+        if not self.exists:
+            raise DeviceError("device has not been created", self.path)
+
+        # Make a best effort attempt to do the eject.  If it fails, it's not
+        # critical.
+        fd = os.open(self.path, os.O_RDONLY | os.O_NONBLOCK)
+
+        #try:
+        #    _isys.ejectcdrom(fd)
+        #except SystemError as e:
+        #    log.warning("error ejecting cdrom %s: %s" % (self.name, e))
+
+        os.close(fd)
+
+class DMDevice(StorageDevice):
+    """ A device-mapper device """
+    _type = "dm"
+    _devDir = "/dev/mapper"
+
+    def __init__(self, installer, name, format=None, size=None, dmUuid=None,
+                 target=None, exists=None, parents=None, sysfsPath=''):
+        """ Create a DMDevice instance.
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+
+            Keyword Arguments:
+
+                target -- the device-mapper target type (string)
+                size -- the device's size (units/format TBD)
+                dmUuid -- the device's device-mapper UUID
+                sysfsPath -- sysfs device path
+                format -- a DeviceFormat instance
+                parents -- a list of required Device instances
+                exists -- indicates whether this is an existing device
+        """
+        StorageDevice.__init__(self, installer, name, format=format, size=size,
+                               exists=exists,
+                               parents=parents, sysfsPath=sysfsPath)
+        self.target = target
+        self.dmUuid = dmUuid
+
+    def __str__(self):
+        s = StorageDevice.__str__(self)
+        s += ("  target = %(target)s  dmUuid = %(dmUuid)s" %
+              {"target": self.target, "dmUuid": self.dmUuid})
+        return s
+
+    @property
+    def fstabSpec(self):
+        """ Return the device specifier for use in /etc/fstab. """
+        return self.path
+
+    def updateSysfsPath(self):
+        """ Update this device's sysfs path. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        if self.status:
+            dm_node = self.getDMNode()
+            path = os.path.join("/sys", self.sysfsBlockDir, dm_node)
+            self.sysfsPath = os.path.realpath(path)[4:]
+        else:
+            self.sysfsPath = ''
+
+    #def getTargetType(self):
+    #    return dm.getDmTarget(name=self.name)
+
+    def getDMNode(self):
+        """ Return the dm-X (eg: dm-0) device node for this device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        return dm.dm_node_from_name(self.name)
+
+    def _setName(self, name):
+        """ Set the device's map name. """
+        if self.status:
+            raise DeviceError("device is active")
+
+        self._name = name
+        #self.sysfsPath = "/dev/disk/by-id/dm-name-%s" % self.name
+
+    name = property(lambda d: d._name,
+                    lambda d,n: d._setName(n))
+
+
+class LVMVolumeGroupDevice(DMDevice):
+    """ An LVM Volume Group
+
+        XXX Maybe this should inherit from StorageDevice instead of
+            DMDevice since there's no actual device.
+    """
+    _type = "lvmvg"
+
+    def __init__(self, installer, name, parents, size=None, free=None,
+                 peSize=None, peCount=None, peFree=None, pvCount=None,
+                 lvNames=[], uuid=None, exists=None, sysfsPath=''):
+        """ Create a LVMVolumeGroupDevice instance.
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+                parents -- a list of physical volumes (StorageDevice)
+
+            Keyword Arguments:
+
+                peSize -- physical extent size (in MB)
+                exists -- indicates whether this is an existing device
+                sysfsPath -- sysfs device path
+
+                For existing VG's only:
+
+                    size -- the VG's size (in MB)
+                    free -- amount of free space in the VG
+                    peFree -- number of free extents
+                    peCount -- total number of extents
+                    pvCount -- number of PVs in this VG
+                    lvNames -- the names of this VG's LVs
+                    uuid -- the VG's UUID
+
+        """
+        self.pvClass = get_device_format_class("lvmpv")
+        if not self.pvClass:
+            raise DeviceError("cannot find 'lvmpv' class")
+
+        if isinstance(parents, list):
+            for dev in parents:
+                if not isinstance(dev.format, self.pvClass):
+                    raise ValueError("constructor requires a list of PVs")
+        elif not isinstance(parents.format, self.pvClass):
+            raise ValueError("constructor requires a list of PVs")
+
+        DMDevice.__init__(self, installer, name, parents=parents,
+                          exists=exists, sysfsPath=sysfsPath)
+
+        self.uuid = uuid
+        self.free = numeric_type(free)
+        self.peSize = numeric_type(peSize)
+        self.peCount = numeric_type(peCount)
+        self.peFree = numeric_type(peFree)
+        self.pvCount = numeric_type(pvCount)
+        self.lvNames = lvNames
+
+        # circular references, here I come
+        self._lvs = []
+
+        # TODO: validate peSize if given
+        if not self.peSize:
+            self.peSize = 4.0   # MB
+
+        #self.probe()
+
+    def __str__(self):
+        s = DMDevice.__str__(self)
+        s += ("  free = %(free)s  PE Size = %(peSize)s  PE Count = %(peCount)s\n"
+              "  PE Free = %(peFree)s  PV Count = %(pvCount)s\n"
+              "  LV Names = %(lvNames)s  modified = %(modified)s\n"
+              "  extents = %(extents)s  free space = %(freeSpace)s\n"
+              "  free extents = %(freeExtents)s\n"
+              "  PVs = %(pvs)s\n"
+              "  LVs = %(lvs)s" %
+              {"free": self.free, "peSize": self.peSize, "peCount": self.peCount,
+               "peFree": self.peFree, "pvCount": self.pvCount,
+               "lvNames": self.lvNames, "modified": self.isModified,
+               "extents": self.extents, "freeSpace": self.freeSpace,
+               "freeExtents": self.freeExtents, "pvs": self.pvs, "lvs": self.lvs})
+        return s
+
+    def probe(self):
+        """ Probe for any information about this device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+    @property
+    def status(self):
+        """ The device's status (True means active). """
+        if not self.exists:
+            return False
+
+        # certainly if any of this VG's LVs are active then so are we
+        for lv in self.lvs:
+            if lv.status:
+                return True
+
+        # if any of our PVs are not active then we cannot be
+        for pv in self.pvs:
+            if not pv.status:
+                return False
+
+        # if we are missing some of our PVs we cannot be active
+        if len(self.pvs) != self.pvCount:
+            return False
+
+        return True
+
+    def _addDevice(self, device):
+        """ Add a new physical volume device to the volume group.
+
+            XXX This is for use by device probing routines and is not
+                intended for modification of the VG.
+        """
+        if not self.exists:
+            raise DeviceError("device does not exist")
+
+        if not isinstance(device.format, self.pvClass):
+            raise ValueError("addDevice requires a PV arg")
+
+        if self.uuid and device.format.vgUuid != self.uuid:
+            raise ValueError("UUID mismatch")
+
+        if device in self.pvs:
+            raise ValueError("device is already a member of this VG")
+
+        self.parents.append(device)
+        device.addChild()
+
+        # now see if the VG can be activated
+        if len(self.parents) == self.pvCount:
+            self.setup()
+
+    def _removeDevice(self, device):
+        """ Remove a physical volume from the volume group.
+
+            This is for cases like clearing of preexisting partitions.
+        """
+        try:
+            self.parents.remove(device)
+        except ValueError, e:
+            raise ValueError("cannot remove non-member PV device from VG")
+
+        device.removeChild()
+
+    def setup(self, intf=None):
+        """ Open, or set up, a device.
+
+            XXX we don't do anything like "vgchange -ay" because we don't
+                want all of the LVs activated, just the VG itself.
+        """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        if self.status:
+            return
+
+        if len(self.parents) < self.pvCount:
+            raise DeviceError("cannot activate VG with missing PV(s)")
+
+        self.setupParents()
+
+    def teardown(self, recursive=None):
+        """ Close, or tear down, a device. """
+        if not self.exists and not recursive:
+            raise DeviceError("device has not been created")
+
+        if self.status:
+            lvm.vgdeactivate(self.name)
+
+        if recursive:
+            self.teardownParents(recursive=recursive)
+
+    def create(self, intf=None):
+        """ Create the device. """
+        if self.exists:
+            raise DeviceError("device already exists")
+
+        pv_list = []
+        #for pv in self.parents:
+            # This is a little bit different from other devices in that
+            # for VG we need the PVs to be formatted before we can create
+            # the VG.
+        #    pv.create()
+        #    pv.format.create()
+        #    pv_list.append(pv.path)
+        pv_list = [pv.path for pv in self.parents]
+        self.createParents()
+        self.setupParents()
+        lvm.vgcreate(self.name, pv_list, self.peSize)
+        # FIXME set / update self.uuid here
+        self.exists = True
+        self.setup()
+
+    def destroy(self):
+        """ Destroy the device. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        # set up the pvs since lvm needs access to them to do the vgremove
+        self.setupParents()
+
+        # this sometimes fails for some reason.
+        try:
+            lvm.vgreduce(self.name, [], rm=True)
+            lvm.vgremove(self.name)
+        except lvm.LVMError:
+            raise DeviceError("Could not completely remove VG %s" % self.name)
+        finally:
+            self.notifyKernel()
+            self.exists = False
+
+    def reduce(self, pv_list):
+        """ Remove the listed PVs from the VG. """
+        if not self.exists:
+            raise DeviceError("device has not been created")
+
+        lvm.vgreduce(self.name, pv_list)
+        # XXX do we need to notify the kernel?
+
+    def _addLogVol(self, lv):
+        """ Add an LV to this VG. """
+        if lv in self._lvs:
+            raise ValueError("lv is already part of this vg")
+
+        # verify we have the space, then add it
+        # do not verify for growing vg (because of ks)
+        if not lv.exists and \
+           not [pv for pv in self.pvs if getattr(pv, "req_grow", None)] and \
+           lv.size > self.freeSpace:
+            raise DeviceError("new lv is too large to fit in free space")
+
+        self._lvs.append(lv)
+
+    def _removeLogVol(self, lv):
+        """ Remove an LV from this VG. """
+        if lv not in self.lvs:
+            raise ValueError("specified lv is not part of this vg")
+
+        self._lvs.remove(lv)
+
+    def _addPV(self, pv):
+        """ Add a PV to this VG. """
+        if pv in self.pvs:
+            raise ValueError("pv is already part of this vg")
+
+        # for the time being we will not allow vgextend
+        if self.exists:
+            raise DeviceError("cannot add pv to existing vg")
+
+        self.parents.append(pv)
+        pv.addChild()
+
+    def _removePV(self, pv):
+        """ Remove an PV from this VG. """
+        if not pv in self.pvs:
+            raise ValueError("specified pv is not part of this vg")
+
+        # for the time being we will not allow vgreduce
+        if self.exists:
+            raise DeviceError("cannot remove pv from existing vg")
+
+        self.parents.remove(pv)
+        pv.removeChild()
+
+    # We can't rely on lvm to tell us about our size, free space, &c
+    # since we could have modifications queued, unless the VG and all of
+    # its PVs already exist.
+    #
+    #        -- liblvm may contain support for in-memory devices
+
+    @property
+    def isModified(self):
+        """ Return True if the VG has changes queued that LVM is unaware of. """
+        modified = True
+        if self.exists and not filter(lambda d: not d.exists, self.pvs):
+            modified = False
+
+        return modified
+
+    @property
+    def size(self):
+        """ The size of this VG """
+        # TODO: just ask lvm if isModified returns False
+
+        # sum up the sizes of the PVs and align to pesize
+        size = 0
+        for pv in self.pvs:
+            size += max(0, self.align(pv.size - pv.format.peStart))
+
+        return size
+
+    @property
+    def extents(self):
+        """ Number of extents in this VG """
+        # TODO: just ask lvm if isModified returns False
+
+        return self.size / self.peSize
+
+    @property
+    def freeSpace(self):
+        """ The amount of free space in this VG (in MB). """
+        # TODO: just ask lvm if isModified returns False
+
+        # total the sizes of any LVs
+        used = 0
+        size = self.size
+        self.installer.log.debug("%s size is %dMB" % (self.name, size))
+        for lv in self.lvs:
+            self.installer.log.debug("lv %s (%s) uses %dMB" % (lv.name, lv, lv.size))
+            used += self.align(lv.size, roundup=True)
+
+        free = self.size - used
+        self.installer.log.debug("vg %s has %dMB free" % (self.name, free))
+        return free
+
+    @property
+    def freeExtents(self):
+        """ The number of free extents in this VG. """
+        # TODO: just ask lvm if isModified returns False
+        return self.freeSpace / self.peSize
+
+    def align(self, size, roundup=None):
+        """ Align a size to a multiple of physical extent size. """
+        size = numeric_type(size)
+
+        if roundup:
+            round = math.ceil
+        else:
+            round = math.floor
+
+        # we want Kbytes as a float for our math
+        size *= 1024.0
+        pesize = self.peSize * 1024.0
+        return long((round(size / pesize) * pesize) / 1024)
+
+    @property
+    def pvs(self):
+        """ A list of this VG's PVs """
+        return self.parents[:]  # we don't want folks changing our list
+
+    @property
+    def lvs(self):
+        """ A list of this VG's LVs """
+        return self._lvs[:]     # we don't want folks changing our list
+
+    @property
+    def complete(self):
+        """Check if the vg has all its pvs in the system
+        Return True if complete.
+        """
+        return len(self.pvs) == self.pvCount or not self.exists
+
+class LVMLogicalVolumeDevice(DMDevice):
+    """ An LVM Logical Volume """
+    _type = "lvmlv"
+    _resizable = True
+
+    def __init__(self, installer, name, vgdev, size=None, uuid=None,
+                 format=None, exists=None, sysfsPath='',
+                 grow=None, maxsize=None, percent=None):
+        """ Create a LVMLogicalVolumeDevice instance.
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+                vgdev -- volume group (LVMVolumeGroupDevice instance)
+
+            Keyword Arguments:
+
+                size -- the device's size (in MB)
+                uuid -- the device's UUID
+                sysfsPath -- sysfs device path
+                format -- a DeviceFormat instance
+                exists -- indicates whether this is an existing device
+
+                For new (non-existent) LVs only:
+
+                    grow -- whether to grow this LV
+                    maxsize -- maximum size for growable LV (in MB)
+                    percent -- percent of VG space to take
+
+        """
+        if isinstance(vgdev, list):
+            if len(vgdev) != 1:
+                raise ValueError("constructor requires a single LVMVolumeGroupDevice instance")
+            elif not isinstance(vgdev[0], LVMVolumeGroupDevice):
+                raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
+        elif not isinstance(vgdev, LVMVolumeGroupDevice):
+            raise ValueError("constructor requires a LVMVolumeGroupDevice instance")
+        DMDevice.__init__(self, installer, name, size=size, format=format,
+                          sysfsPath=sysfsPath, parents=vgdev,
+                          exists=exists)
+
+        self.uuid = uuid
+
+        self.req_grow = None
+        self.req_max_size = 0
+        self.req_size = 0
+        self.req_percent = 0
+
+        if not self.exists:
+            self.req_grow = grow
+            self.req_max_size = numeric_type(maxsize)
+            # XXX should we enforce that req_size be pe-aligned?
+            self.req_size = self._size
+            self.req_percent = numeric_type(percent)
+
+        # here we go with the circular references
+        self.vg._addLogVol(self)
+
+    def __str__(self):
+        s = DMDevice.__str__(self)
+        s += ("  VG device = %(vgdev)r  percent = %(percent)s" %
+              {"vgdev": self.vg, "percent": self.req_percent})
+        return s
+
+    def _setSize(self, size):
+        size = self.vg.align(numeric_type(size))
+        self.installer.log.debug("Trying to set lv %s size to %dMB" % (self.name, size))
+        if size <= (self.vg.freeSpace + self._size):
+            self._size = size
+            self.targetSize = size
+        else:
+            self.installer.log.debug("Failed to set size: %dMB short" % (size - (self.vg.freeSpace + self._size),))
+            raise ValueError("Not enough free space in volume group")
+
+    size = property(StorageDevice._getSize, _setSize)
+
+    @property
+    def vg(self):
+        """ This Logical Volume's Volume Group. """
+        return self.parents[0]
+
+    @property
+    def path(self):
+        """ Device node representing this device. """
+        # Thank you lvm for this lovely hack.
+        return "%s/%s-%s" % (self._devDir, self.vg.name.replace("-","--"),
+                self._name.replace("-","--"))
+
+    def getDMNode(self):
+        """ Return the dm-X (eg: dm-0) device node for this device. """
+        # Thank you lvm for this lovely hack.
+        if not self.exists:
+            raise DeviceError("Device has not been created", self.path)
+
+        return dm.dm_node_from_name("%s-%s" % (self.vg.name.replace("-","--"), \
+                self._name.replace("-","--")))
+
+    @property
+    def name(self):
+        """ This device's name. """
+        return "%s-%s" % (self.vg.name, self._name)
+
+    @property
+    def lvname(self):
+        """ The LV's name (not including VG name). """
+        return self._name
+
+    @property
+    def complete(self):
+        """ Test if vg exits and if it has all pvs. """
+        return self.vg.complete
+
+    def setup(self, intf=None):
+        """ Open, or set up, a device. """
+        if not self.exists:
+            raise DeviceError("Device has not been created", self.path)
+
+        if self.status:
+            return
+
+        self.vg.setup()
+        lvm.lvactivate(self.vg.name, self._name)
+
+        # we always probe since the device may not be set up when we want
+        # information about it
+        self._size = self.currentSize
+
+    def teardown(self, recursive=None):
+        """ Close, or tear down, a device. """
+        if not self.exists and not recursive:
+            raise DeviceError("Device has not been created", self.path)
+
+        if self.status and self.format.exists:
+            self.format.teardown()
+            udev_settle(timeout=10)
+
+        if self.status:
+            lvm.lvdeactivate(self.vg.name, self._name)
+
+        if recursive:
+            # It's likely that teardown of a VG will fail due to other
+            # LVs being active (filesystems mounted, &c), so don't let
+            # it bring everything down.
+            try:
+                self.vg.teardown(recursive=recursive)
+            except Exception as e:
+                log.debug("vg %s teardown failed; continuing" % self.vg.name)
+
+    def create(self, intf=None):
+        """ Create the device. """
+        if self.exists:
+            raise DeviceError("Device already exists", self.path)
+
+        self.createParents()
+        self.setupParents()
+
+        # should we use --zero for safety's sake?
+        lvm.lvcreate(self.vg.name, self._name, self.size)
+        # FIXME set / update self.uuid here
+        self.exists = True
+        self.setup()
+
+    def destroy(self):
+        """ Destroy the device. """
+        if not self.exists:
+            raise DeviceError("Device has not been created", self.path)
+
+        self.teardown()
+        # set up the vg's pvs so lvm can remove the lv
+        self.vg.setupParents()
+        lvm.lvremove(self.vg.name, self._name)
+        self.exists = False
+
+    def resize(self, intf=None):
+        # XXX resize format probably, right?
+        if not self.exists:
+            raise DeviceError("Device has not been created", self.path)
+
+        # Setup VG parents (in case they are dmraid partitions for example)
+        self.vg.setupParents()
+
+        if self.format.exists:
+            self.format.teardown()
+
+        udev_settle(timeout=10)
+        lvm.lvresize(self.vg.name, self._name, self.size)
diff --git a/pkgs/core/pomona/src/storage_old/devicetree.py b/pkgs/core/pomona/src/storage_old/devicetree.py
new file mode 100644 (file)
index 0000000..042941c
--- /dev/null
@@ -0,0 +1,536 @@
+#!/usr/bin/python
+
+import os
+import block
+
+import formats
+
+from devicelibs import lvm
+from devices import *
+from errors import *
+from udev import *
+
+class DeviceTree:
+    def __init__(self, installer):
+        self.installer = installer
+        self.storage = self.installer.ds.storage
+
+        self._devices = []
+        self._actions = []
+
+        self._ignoredDisks = []
+        self.immutableDevices = []
+        for disk in self.storage.ignoredDisks:
+            self.addIgnoredDisk(disk)
+
+    def addIgnoredDisk(self, disk):
+        self._ignoredDisks.append(disk)
+        lvm.lvm_cc_addFilterRejectRegexp(disk)
+
+    def populate(self):
+        """ Locate all storage devices. """
+        # each iteration scans any devices that have appeared since the
+        # previous iteration
+        old_devices = []
+        ignored_devices = []
+        while True:
+            devices = []
+            new_devices = udev_get_block_devices()
+
+            for new_device in new_devices:
+                found = False
+                for old_device in old_devices:
+                    if old_device['name'] == new_device['name']:
+                        found = True
+                        break
+
+                if not found:
+                    devices.append(new_device)
+
+            if len(devices) == 0:
+                # nothing is changing -- we are finished building devices
+                break
+
+            old_devices = new_devices
+            self.installer.log.info("Devices to scan: %s" % [d['name'] for d in devices])
+            for dev in devices:
+                self.addUdevDevice(dev)
+
+        # After having the complete tree we make sure that the system
+        # inconsistencies are ignored or resolved.
+        #for leaf in self.leaves:
+        #    self._handleInconsistencies(leaf)
+
+        #self.teardownAll()
+        try:
+            os.unlink("/etc/mdadm.conf")
+        except OSError:
+            pass
+
+    @property
+    def devices(self):
+        """ Dict with device path keys and Device values. """
+        devices = {}
+
+        for device in self._devices:
+            if device.path in devices:
+                raise DeviceTreeError("duplicate paths in device tree")
+
+            devices[device.path] = device
+
+        return devices
+
+    @property
+    def filesystems(self):
+        """ List of filesystems. """
+        #""" Dict with mountpoint keys and filesystem values. """
+        filesystems = []
+        for dev in self.leaves:
+            if dev.format and getattr(dev.format, 'mountpoint', None):
+                filesystems.append(dev.format)
+
+        return filesystems
+
+    @property
+    def leaves(self):
+        """ List of all devices upon which no other devices exist. """
+        leaves = [d for d in self._devices if d.isleaf]
+        return leaves
+
+    def teardownAll(self):
+        """ Run teardown methods on all devices. """
+        for device in self.leaves:
+            try:
+                device.teardown(recursive=True)
+            except (DeviceError, DeviceFormatError, LVMError) as e:
+                self.installer.log.info("Teardown of %s failed: %s" % (device.name, e))
+
+    def _addDevice(self, newdev):
+        """ Add a device to the tree.
+
+            Raise ValueError if the device's identifier is already
+            in the list.
+        """
+        if newdev.path in [d.path for d in self._devices]:
+            raise ValueError("device is already in tree")
+
+        # make sure this device's parent devices are in the tree already
+        for parent in newdev.parents:
+            if parent not in self._devices:
+                raise DeviceTreeError("parent device not in tree")
+
+        self._devices.append(newdev)
+        self.installer.log.info("Added %s (%s) to device tree" % (newdev.name, newdev.type))
+        #self.installer.log.info("    Status: %s" % newdev.status)
+        #self.installer.log.info("    Format: %s" % newdev.format.type)
+
+    def _removeDevice(self, dev, force=None, moddisk=True):
+        """ Remove a device from the tree.
+
+            Only leaves may be removed.
+        """
+        if dev not in self._devices:
+            raise ValueError("Device '%s' not in tree" % dev.name)
+
+        if not dev.isleaf and not force:
+            self.installer.log.debug("%s has %d kids" % (dev.name, dev.kids))
+            raise ValueError("Cannot remove non-leaf device '%s'" % dev.name)
+
+        # if this is a partition we need to remove it from the parted.Disk
+        if moddisk and isinstance(dev, PartitionDevice) and \
+                dev.disk is not None:
+            # if this partition hasn't been allocated it could not have
+            # a disk attribute
+            if dev.partedPartition.type == parted.PARTITION_EXTENDED and \
+                    len(dev.disk.partedDisk.getLogicalPartitions()) > 0:
+                raise ValueError("Cannot remove extended partition %s.  "
+                        "Logical partitions present." % dev.name)
+
+            dev.disk.partedDisk.removePartition(dev.partedPartition)
+
+        self._devices.remove(dev)
+        self.installer.log.debug("Removed %s (%s) from device tree" % (dev.name,
+                                                                       dev.type))
+
+        for parent in dev.parents:
+            # Will this cause issues with garbage collection?
+            #   Do we care about garbage collection? At all?
+            parent.removeChild()
+
+    def isIgnored(self, info):
+        """ Return True if info is a device we should ignore.
+
+            Arguments:
+
+                info -- a dict representing a udev db entry
+
+            TODO:
+
+                - filtering of SAN/FC devices
+                - filtering by driver?
+
+        """
+        sysfs_path = udev_device_get_sysfs_path(info)
+        name = udev_device_get_name(info)
+        if not sysfs_path:
+            return None
+
+        if name in self._ignoredDisks:
+            return True
+
+        for ignored in self._ignoredDisks:
+            if ignored == os.path.basename(os.path.dirname(sysfs_path)):
+                # this is a partition on a disk in the ignore list
+                return True
+
+        # Ignore partitions found on the raw disks which are part of a
+        # dmraidset
+        for set in self.getDevicesByType("dm-raid array"):
+            for disk in set.parents:
+                if disk.name == os.path.basename(os.path.dirname(sysfs_path)):
+                    return True
+
+        # Ignore loop and ram devices, we normally already skip these in
+        # udev.py: enumerate_block_devices(), but we can still end up trying
+        # to add them to the tree when they are slaves of other devices, this
+        # happens for example with the livecd
+        if name.startswith("loop") or name.startswith("ram"):
+            return True
+
+        # FIXME: check for virtual devices whose slaves are on the ignore list
+
+    def getDeviceByName(self, name):
+        found = None
+        for device in self._devices:
+            if device.name == name:
+                found = device
+                break
+        return found
+
+    def getDevicesByType(self, device_type):
+        # TODO: expand this to catch device format types
+        return [d for d in self._devices if d.type == device_type]
+
+    def getDevicesByInstance(self, device_class):
+        return [d for d in self._devices if isinstance(d, device_class)]
+
+    def registerAction(self, action):
+        """ Register an action to be performed at a later time.
+
+            Modifications to the Device instance are handled before we
+            get here.
+        """
+        if (action.isDestroy() or action.isResize() or \
+            (action.isCreate() and action.isFormat())) and \
+           action.device not in self._devices:
+            raise DeviceTreeError("device is not in the tree")
+        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")
+            if action.device in self._devices:
+                self._removeDevice(action.device)
+            for d in self._devices:
+                if d.path == action.device.path:
+                    self._removeDevice(d)
+
+        if action.isCreate() and action.isDevice():
+            self._addDevice(action.device)
+        elif action.isDestroy() and action.isDevice():
+            self._removeDevice(action.device)
+        elif action.isCreate() and action.isFormat():
+            if isinstance(action.device.format, formats.fs.FS) and \
+               action.device.format.mountpoint in self.filesystems:
+                raise DeviceTreeError("mountpoint already in use")
+
+        self.installer.log.debug("Registered action: %s" % action)
+        self._actions.append(action)
+
+    def addUdevDevice(self, info):
+        # FIXME: this should be broken up into more discrete chunks
+        name = udev_device_get_name(info)
+        uuid = udev_device_get_uuid(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+
+        if self.isIgnored(info):
+            self.installer.log.debug("Ignoring %s (%s)" % (name, sysfs_path))
+            return
+
+        self.installer.log.debug("Scanning %s (%s)..." % (name, sysfs_path))
+        device = self.getDeviceByName(name)
+
+        #
+        # The first step is to either look up or create the device
+        #
+        if udev_device_is_dm(info):
+            # try to look up the device
+            if device is None and uuid:
+                # try to find the device by uuid
+                device = self.getDeviceByUuid(uuid)
+
+            if device is None:
+                device = self.addUdevDMDevice(info)
+        elif udev_device_is_md(info):
+            if device is None and uuid:
+                # try to find the device by uuid
+                device = self.getDeviceByUuid(uuid)
+
+            if device is None:
+                device = self.addUdevMDDevice(info)
+        elif udev_device_is_cdrom(info):
+            if device is None:
+                device = self.addUdevOpticalDevice(info)
+        elif udev_device_is_dmraid(info):
+            # This is special handling to avoid the "unrecognized disklabel"
+            # code since dmraid member disks won't have a disklabel. We
+            # use a StorageDevice because DiskDevices need disklabels.
+            # Quite lame, but it doesn't matter much since we won't use
+            # the StorageDevice instances for anything.
+            if device is None:
+                device = StorageDevice(name,
+                                major=udev_device_get_major(info),
+                                minor=udev_device_get_minor(info),
+                                sysfsPath=sysfs_path, exists=True)
+                self._addDevice(device)
+        elif udev_device_is_disk(info):
+            if device is None:
+                device = self.addUdevDiskDevice(info)
+        elif udev_device_is_partition(info):
+            if device is None:
+                device = self.addUdevPartitionDevice(info)
+
+        # now handle the device's formatting
+        self.handleUdevDeviceFormat(info, device)
+
+    def addUdevDiskDevice(self, info):
+        name = udev_device_get_name(info)
+        uuid = udev_device_get_uuid(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        device = None
+
+        try:
+            device = DiskDevice(self.installer, name,
+                              major=udev_device_get_major(info),
+                              minor=udev_device_get_minor(info),
+                              sysfsPath=sysfs_path,
+                              initlabel=False)
+        except DeviceUserDeniedFormatError: #drive not initialized?
+            self.addIgnoredDisk(name)
+            return
+
+        self._addDevice(device)
+        return device
+
+    def addUdevPartitionDevice(self, info):
+        name = udev_device_get_name(info)
+        uuid = udev_device_get_uuid(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        device = None
+
+        disk_name = os.path.basename(os.path.dirname(sysfs_path))
+        disk = self.getDeviceByName(disk_name)
+
+        if disk is None:
+            # create a device instance for the disk
+            path = os.path.dirname(os.path.realpath(sysfs_path))
+            new_info = udev_get_block_device(path)
+            if new_info:
+                self.addUdevDevice(new_info)
+                disk = self.getDeviceByName(disk_name)
+
+            if disk is None:
+                # if the current device is still not in
+                # the tree, something has gone wrong
+                self.installer.log.error("Failure scanning device %s" % disk_name)
+                return
+
+        try:
+            device = PartitionDevice(self.installer, name, sysfsPath=sysfs_path,
+                                     major=udev_device_get_major(info),
+                                     minor=udev_device_get_minor(info),
+                                     exists=True, parents=[disk])
+        except DeviceError:
+            # corner case sometime the kernel accepts a partition table
+            # which gets rejected by parted, in this case we will
+            # prompt to re-initialize the disk, so simply skip the
+            # faulty partitions.
+            return
+
+        self._addDevice(device)
+        return device
+
+    def addUdevOpticalDevice(self, info):
+        # Looks like if it has ID_INSTANCE=0:1 we can ignore it.
+        device = OpticalDevice(self.installer, udev_device_get_name(info),
+                               major=udev_device_get_major(info),
+                               minor=udev_device_get_minor(info),
+                               sysfsPath=udev_device_get_sysfs_path(info))
+        self._addDevice(device)
+        return device
+
+    def handleUdevDeviceFormat(self, info, device):
+        #log.debug("%s" % info)
+        name = udev_device_get_name(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        uuid = udev_device_get_uuid(info)
+        label = udev_device_get_label(info)
+        format_type = udev_device_get_format(info)
+
+        format = None
+        if (not device) or (not format_type) or device.format.type:
+            # this device has no formatting or it has already been set up
+            # FIXME: this probably needs something special for disklabels
+            self.installer.log.debug("no type or existing type for %s, bailing" % (name,))
+            return
+
+        # set up the common arguments for the format constructor
+        args = [format_type]
+        kwargs = {"uuid": uuid,
+                  "label": label,
+                  "device": device.path,
+                  "exists": True}
+
+        # set up type-specific arguments for the format constructor
+        if format_type == "crypto_LUKS":
+            # luks/dmcrypt
+            kwargs["name"] = "luks-%s" % uuid
+        elif format_type == "linux_raid_member":
+            # mdraid
+            try:
+                kwargs["mdUuid"] = udev_device_get_md_uuid(info)
+            except KeyError:
+                self.installer.log.debug("mdraid member %s has no md uuid" % name)
+        elif format_type == "LVM2_member":
+            # lvm
+            try:
+                kwargs["vgName"] = udev_device_get_vg_name(info)
+            except KeyError as e:
+                self.installer.log.debug("PV %s has no vg_name" % name)
+            try:
+                kwargs["vgUuid"] = udev_device_get_vg_uuid(info)
+            except KeyError:
+                self.installer.log.debug("PV %s has no vg_uuid" % name)
+            try:
+                kwargs["peStart"] = udev_device_get_pv_pe_start(info)
+            except KeyError:
+                self.installer.log.debug("PV %s has no pe_start" % name)
+
+        try:
+            self.installer.log.debug("type detected on '%s' is '%s'" % (name, format_type,))
+            device.format = formats.getFormat(format_type, *args, **kwargs)
+        except FSError, e:
+            self.installer.log.debug("type '%s' on '%s' invalid, assuming no format - %s" %
+                      (format_type, name, e,))
+            device.format = formats.DeviceFormat(self.installer)
+            return
+
+        #
+        # now do any special handling required for the device's format
+        #
+        #if device.format.type == "luks":
+        #    self.handleUdevLUKSFormat(info, device)
+        #elif device.format.type == "mdmember":
+        #    self.handleUdevMDMemberFormat(info, device)
+        #elif device.format.type == "dmraidmember":
+        #    self.handleUdevDMRaidMemberFormat(info, device)
+        #elif device.format.type == "lvmpv":
+        #    self.handleUdevLVMPVFormat(info, device)
+
+    def handleUdevDMRaidMemberFormat(self, info, device):
+        name = udev_device_get_name(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        uuid = udev_device_get_uuid(info)
+        major = udev_device_get_major(info)
+        minor = udev_device_get_minor(info)
+
+        def _all_ignored(rss):
+            retval = True
+            for rs in rss:
+                if rs.name not in self._ignoredDisks:
+                    retval = False
+                    break
+            return retval
+
+        # Have we already created the DMRaidArrayDevice?
+        rss = block.getRaidSetFromRelatedMem(uuid=uuid, name=name,
+                                            major=major, minor=minor)
+        if len(rss) == 0:
+            # we ignore the device in the hope that all the devices
+            # from this set will be ignored.
+            # FIXME: Can we reformat a raid device?
+            self.addIgnoredDisk(device.name)
+            return
+
+        # We ignore the device if all the rss are in self._ignoredDisks
+        if _all_ignored(rss):
+            self.addIgnoredDisk(device.name)
+            return
+
+        for rs in rss:
+            dm_array = self.getDeviceByName(rs.name)
+            if dm_array is not None:
+                # We add the new device.
+                dm_array._addDevice(device)
+            else:
+                # Activate the Raid set.
+                rs.activate(mknod=True)
+
+                # Create the DMRaidArray
+                if self.zeroMbr:
+                    cb = lambda: True
+                else:
+                    cb = lambda: questionInitializeDisk(self.intf,
+                                                        rs.name)
+
+                # Create the DMRaidArray
+                if not self.clearPartDisks or \
+                   rs.name in self.clearPartDisks:
+                    # if the disk contains protected partitions
+                    # we will not wipe the disklabel even if
+                    # clearpart --initlabel was specified
+                    initlabel = self.reinitializeDisks
+                    for protected in self.protectedPartitions:
+                        disk_name = re.sub(r'p\d+$', '', protected)
+                        if disk_name != protected and \
+                           disk_name == rs.name:
+                            initlabel = False
+                            break
+
+                try:
+                    dm_array = DMRaidArrayDevice(rs.name,
+                                                 raidSet=rs,
+                                                 parents=[device],
+                                                 initcb=cb,
+                                                 initlabel=initlabel)
+
+                    self._addDevice(dm_array)
+                    # Use the rs's object on the device.
+                    # pyblock can return the memebers of a set and the
+                    # device has the attribute to hold it.  But ATM we
+                    # are not really using it. Commenting this out until
+                    # we really need it.
+                    #device.format.raidmem = block.getMemFromRaidSet(dm_array,
+                    #        major=major, minor=minor, uuid=uuid, name=name)
+                except DeviceUserDeniedFormatError:
+                    # We should ignore the dmraid and its components
+                    self.addIgnoredDisk(rs.name)
+                    if _all_ignored(rss):
+                        self.addIgnoredDisk(device.name)
+                    rs.deactivate()
+
+    def getDependentDevices(self, dep):
+        """ Return a list of devices that depend on dep.
+
+            The list includes both direct and indirect dependents.
+        """
+        dependents = []
+
+        # special handling for extended partitions since the logical
+        # partitions and their deps effectively depend on the extended
+        logicals = []
+        if isinstance(dep, PartitionDevice) and dep.partType and \
+           dep.isExtended:
+            # collect all of the logicals on the same disk
+            for part in self.getDevicesByInstance(PartitionDevice):
+                if part.partType and part.isLogical and part.disk == dep.disk:
+                    logicals.append(part)
diff --git a/pkgs/core/pomona/src/storage_old/errors.py b/pkgs/core/pomona/src/storage_old/errors.py
new file mode 100644 (file)
index 0000000..7a02dc0
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/python
+
+class StorageError(Exception):
+    pass
+
+# Device
+class DeviceError(StorageError):
+    pass
+
+class DeviceCreateError(DeviceError):
+    pass
+
+class DeviceDestroyError(DeviceError):
+    pass
+
+class DeviceResizeError(DeviceError):
+    pass
+
+class DeviceSetupError(DeviceError):
+    pass
+
+class DeviceTeardownError(DeviceError):
+    pass
+
+class DeviceUserDeniedFormatError(DeviceError):
+    pass
+
+# DeviceFormat
+class DeviceFormatError(StorageError):
+    pass
+
+class FormatCreateError(DeviceFormatError):
+    pass
+
+class FormatDestroyError(DeviceFormatError):
+    pass
+
+class FormatSetupError(DeviceFormatError):
+    pass
+
+class FormatTeardownError(DeviceFormatError):
+    pass
+
+class DMRaidMemberError(DeviceFormatError):
+    pass
+
+class FSError(DeviceFormatError):
+    pass
+
+class FSResizeError(FSError):
+    pass
+
+class FSMigrateError(FSError):
+    pass
+
+class LUKSError(DeviceFormatError):
+    pass
+
+class MDMemberError(DeviceFormatError):
+    pass
+
+class PhysicalVolumeError(DeviceFormatError):
+    pass
+
+class SwapSpaceError(DeviceFormatError):
+    pass
+
+# devicelibs
+class SwapError(StorageError):
+    pass
+
+class SuspendError(SwapError):
+    pass
+
+class OldSwapError(SwapError):
+    pass
+
+class MDRaidError(StorageError):
+    pass
+
+class DMError(StorageError):
+    pass
+
+class LVMError(StorageError):
+    pass
+
+class CryptoError(StorageError):
+    pass
+
+# DeviceTree
+class DeviceTreeError(StorageError):
+    pass
+
+# DeviceAction
+class DeviceActionError(StorageError):
+    pass
+
+# partitioning
+class PartitioningError(StorageError):
+    pass
+
+class PartitioningWarning(StorageError):
+    pass
+
+# udev
+class UdevError(StorageError):
+    pass
diff --git a/pkgs/core/pomona/src/storage_old/formats/__init__.py b/pkgs/core/pomona/src/storage_old/formats/__init__.py
new file mode 100644 (file)
index 0000000..f73f138
--- /dev/null
@@ -0,0 +1,318 @@
+#!/usr/bin/python
+
+import os
+import copy
+
+device_formats = {}
+def register_device_format(fmt_class):
+    if not issubclass(fmt_class, DeviceFormat):
+        raise ValueError("Argument must be a subclass of DeviceFormat")
+    device_formats[fmt_class._type] = fmt_class
+
+def getFormat(fmt_type, *args, **kwargs):
+    """ Return a DeviceFormat instance based on fmt_type and args.
+
+        Given a device format type and a set of constructor arguments,
+        return a DeviceFormat instance.
+
+        Return None if no suitable format class is found.
+
+        Arguments:
+
+            fmt_type -- the name of the format type (eg: 'ext3', 'swap')
+
+        Keyword Arguments:
+
+            The keyword arguments may vary according to the format type,
+            but here is the common set:
+
+            device -- path to the device on which the format resides
+            uuid -- the UUID of the (preexisting) formatted device
+            exists -- whether or not the format exists on the device
+
+    """
+    fmt_class = get_device_format_class(fmt_type)
+    fmt = None
+    if fmt_class:
+        fmt = fmt_class(*args, **kwargs)
+    try:
+        className = fmt.__class__.__name__
+    except AttributeError:
+        className = None
+    return fmt
+
+def get_device_format_class(fmt_type):
+    """ Return an appropriate format class based on fmt_type. """
+    if not device_formats:
+        collect_device_format_classes()
+
+    fmt = device_formats.get(fmt_type)
+    if not fmt:
+        for fmt_class in device_formats.values():
+            if fmt_type and fmt_type == fmt_class._name:
+                fmt = fmt_class
+                break
+            elif fmt_type in fmt_class._udevTypes:
+                fmt = fmt_class
+                break
+
+    # default to no formatting, AKA "Unknown"
+    if not fmt:
+        fmt = DeviceFormat
+    return fmt
+
+def collect_device_format_classes():
+    """ Pick up all device format classes from this directory.
+
+        Note: Modules must call register_device_format(FormatClass) in
+              order for the format class to be picked up.
+    """
+    dir = os.path.dirname(__file__)
+    for module_file in os.listdir(dir):
+        # make sure we're not importing this module
+        if module_file.endswith(".py") and module_file != __file__:
+            mod_name = module_file[:-3]
+            try:
+                globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1)
+            except ImportError, e:
+                pass
+
+default_fstypes = ("ext4", "ext3", "ext2")
+default_boot_fstypes = ("ext3", "ext2")
+def get_default_filesystem_type(boot=None):
+    if boot:
+        fstypes = default_boot_fstypes
+    else:
+        fstypes = default_fstypes
+
+    for fstype in fstypes:
+        try:
+            supported = get_device_format_class(fstype).supported
+        except AttributeError:
+            supported = None
+
+        if supported:
+            return fstype
+
+    raise DeviceFormatError("None of %s is supported by your kernel" % ",".join(fstypes))
+
+class DeviceFormat(object):
+    """ Generic device format. """
+    _type = None
+    _name = "Unknown"
+    _udevTypes = []
+    partedFlag = None
+    _formattable = False                # can be formatted
+    _supported = False                  # is supported
+    _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
+    _dump = False
+    _check = False
+
+    def __init__(self, installer, *args, **kwargs):
+        """ Create a DeviceFormat instance.
+
+            Keyword Arguments:
+
+                device -- path to the underlying device
+                uuid -- this format's UUID
+                exists -- indicates whether this is an existing format
+
+        """
+        self.installer = installer
+
+        self.device = kwargs.get("device")
+        self.uuid = kwargs.get("uuid")
+        self.exists = kwargs.get("exists")
+        self.options = kwargs.get("options")
+        self._migrate = False
+
+    def __deepcopy__(self, memo):
+        new = self.__class__.__new__(self.__class__)
+        memo[id(self)] = new
+        shallow_copy_attrs = ('installer', 'screen')
+        for (attr, value) in self.__dict__.items():
+            if attr in shallow_copy_attrs:
+                setattr(new, attr, copy.copy(value))
+            else:
+                setattr(new, attr, copy.deepcopy(value, memo))
+
+        return new
+
+    def _setOptions(self, options):
+        self._options = options
+
+    def _getOptions(self):
+        return self._options
+
+    options = property(_getOptions, _setOptions)
+
+    def _setDevice(self, devspec):
+        if devspec and not devspec.startswith("/"):
+            raise ValueError("device must be a fully qualified path: %s" % devspec)
+        self._device = devspec
+
+    def _getDevice(self):
+        return self._device
+
+    device = property(lambda f: f._getDevice(),
+                      lambda f,d: f._setDevice(d),
+                      doc="Full path the device this format occupies")
+
+    @property
+    def name(self):
+        if self._name:
+            name = self._name
+        else:
+            name = self.type
+        return name
+
+    @property
+    def type(self):
+        return self._type
+
+    def probe(self):
+        pass
+
+    def notifyKernel(self):
+        if not self.device:
+            return
+
+        if self.device.startswith("/dev/mapper/"):
+            try:
+                name = dm_node_from_name(os.path.basename(self.device))
+            except Exception, e:
+                self.installer.log.warning("Failed to get dm node for %s" % self.device)
+                return
+        elif self.device:
+            name = os.path.basename(self.device)
+
+        path = get_sysfs_path_by_name(name)
+        try:
+            notify_kernel(path, action="change")
+        except Exception, e:
+            self.installer.log.warning("Failed to notify kernel of change: %s" % e)
+
+    def create(self, *args, **kwargs):
+        # allow late specification of device path
+        device = kwargs.get("device")
+        if device:
+            self.device = device
+
+        if not os.path.exists(self.device):
+            raise FormatCreateError("invalid device specification")
+
+    def destroy(self, *args, **kwargs):
+        # zero out the 1MB at the beginning and end of the device in the
+        # hope that it will wipe any metadata from filesystems that
+        # previously occupied this device
+        self.installer.log.debug("Zeroing out beginning and end of %s..." % self.device)
+        try:
+            fd = os.open(self.device, os.O_RDWR)
+            buf = '\0' * 1024 * 1024
+            os.write(fd, buf)
+            os.lseek(fd, -1024 * 1024, 2)
+            os.write(fd, buf)
+            os.close(fd)
+        except OSError as e:
+            if getattr(e, "errno", None) == 28: # No space left in device
+                pass
+            else:
+                self.installer.log.error("Error zeroing out %s: %s" % (self.device, e))
+            os.close(fd)
+        except Exception as e:
+            self.installer.log.error("Error zeroing out %s: %s" % (self.device, e))
+            os.close(fd)
+
+        self.exists = False
+
+    def setup(self, *args, **kwargs):
+        if not self.exists:
+            raise FormatSetupError("format has not been created")
+
+        if self.status:
+            return
+
+        # allow late specification of device path
+        device = kwargs.get("device")
+        if device:
+            self.device = device
+
+        if not self.device or not os.path.exists(self.device):
+            raise FormatSetupError("invalid device specification")
+
+    def teardown(self, *args, **kwargs):
+        pass
+
+    @property
+    def status(self):
+        return (self.exists and
+                self.__class__ is not DeviceFormat and
+                isinstance(self.device, str) and
+                self.device and
+                os.path.exists(self.device))
+
+    @property
+    def formattable(self):
+        """ Can we create formats of this type? """
+        return self._formattable
+
+    @property
+    def supported(self):
+        """ Is this format a supported type? """
+        return self._supported
+
+    @property
+    def resizable(self):
+        """ Can formats of this type be resized? """
+        return self._resizable
+
+    @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
+
+    @property
+    def migrate(self):
+        return self._migrate
+
+    @property
+    def linuxNative(self):
+        """ Is this format type native to linux? """
+        return self._linuxNative
+
+    @property
+    def mountable(self):
+        """ Is this something we can mount? """
+        return False
+
+    @property
+    def dump(self):
+        """ Whether or not this format will be dumped by dump(8). """
+        return self._dump
+
+    @property
+    def check(self):
+        """ Whether or not this format is checked on boot. """
+        return self._check
+
+    @property
+    def maxSize(self):
+        """ Maximum size (in MB) for this format type. """
+        return self._maxSize
+
+    @property
+    def minSize(self):
+        """ Minimum size (in MB) for this format type. """
+        return self._minSize
+
+
+collect_device_format_classes()
diff --git a/pkgs/core/pomona/src/storage_old/formats/fs.py b/pkgs/core/pomona/src/storage_old/formats/fs.py
new file mode 100644 (file)
index 0000000..2eace61
--- /dev/null
@@ -0,0 +1,929 @@
+#!/usr/bin/python
+
+import os
+import tempfile
+
+import isys
+
+import util
+
+from ..errors import *
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+fs_configs = {}
+
+def get_kernel_filesystems():
+    fs_list = []
+    for line in open("/proc/filesystems").readlines():
+        fs_list.append(line.split()[-1])
+    return fs_list
+
+global kernel_filesystems
+kernel_filesystems = get_kernel_filesystems()
+
+class FS(DeviceFormat):
+    """ Filesystem class. """
+    _type = "Abstract Filesystem Class"  # fs type name
+    _mountType = None                    # like _type but for passing to mount
+    _name = None
+    _mkfs = ""                           # mkfs utility
+    _modules = []                        # kernel modules required for support
+    _resizefs = ""                       # resize utility
+    _labelfs = ""                        # labeling utility
+    _fsck = ""                           # fs check utility
+    _migratefs = ""                      # fs migration utility
+    _defaultFormatOptions = []           # default options passed to mkfs
+    _defaultMountOptions = ["defaults"]  # default options passed to mount
+    _defaultLabelOptions = []
+    _defaultCheckOptions = []
+    _defaultMigrateOptions = []
+    _migrationTarget = None
+    lostAndFoundContext = None
+
+    def __init__(self, installer, *args, **kwargs):
+        """ Create a FS instance.
+
+            Keyword Args:
+
+                device -- path to the device containing the filesystem
+                mountpoint -- the filesystem's mountpoint
+                label -- the filesystem label
+                uuid -- the filesystem UUID
+                mountopts -- mount options for the filesystem
+                size -- the filesystem's size in MiB
+                exists -- indicates whether this is an existing filesystem
+
+        """
+        if self.__class__ is FS:
+            raise TypeError("FS is an abstract class.")
+
+        self.installer = installer
+
+        DeviceFormat.__init__(self, self.installer, *args, **kwargs)
+        # TODO: fsprofiles and other ways to add format args
+        self.mountpoint = kwargs.get("mountpoint")
+        self.mountopts = kwargs.get("mountopts")
+        self.label = kwargs.get("label")
+
+        # filesystem size does not necessarily equal device size
+        self._size = kwargs.get("size")
+        self._mountpoint = None     # the current mountpoint when mounted
+        if self.exists:
+            self._size = self._getExistingSize()
+
+        self._targetSize = self._size
+
+        if self.supported:
+            self.loadModule()
+
+    def _setTargetSize(self, newsize):
+        """ Set a target size for this filesystem. """
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if newsize is None:
+            # unset any outstanding resize request
+            self._targetSize = None
+            return
+
+        if not self.minSize < newsize < self.maxSize:
+            raise ValueError("invalid target size request")
+
+        self._targetSize = newsize
+
+    def _getTargetSize(self):
+        """ Get this filesystem's target size. """
+        return self._targetSize
+
+    targetSize = property(_getTargetSize, _setTargetSize,
+                          doc="Target size for this filesystem")
+
+    def _getSize(self):
+        """ Get this filesystem's size. """
+        size = self._size
+        if self.resizable and self.targetSize != size:
+            size = self.targetSize
+        return size
+
+    size = property(_getSize, doc="This filesystem's size, accounting "
+                                  "for pending changes")
+
+    def _getExistingSize(self):
+        """ Determine the size of this filesystem.  Filesystem must
+            exist.
+        """
+        size = 0
+
+        if self.mountable:
+            origMountPoint = self._mountpoint
+
+            tmppath = tempfile.mkdtemp(prefix='getsize-', dir='/tmp')
+            self.mount(mountpoint=tmppath, options="ro")
+            buf = os.statvfs(tmppath)
+            self.unmount()
+            os.rmdir(tmppath)
+
+            self._mountpoint = origMountPoint
+
+            size = (buf.f_frsize * buf.f_blocks) / 1024.0 / 1024.0
+
+        return size
+
+    @property
+    def currentSize(self):
+        """ The filesystem's current actual size. """
+        size = 0
+        if self.exists:
+            size = self._size
+        return float(size)
+
+    def _getFormatOptions(self, options=None):
+        argv = []
+        if options and isinstance(options, list):
+            argv.extend(options)
+        argv.extend(self.defaultFormatOptions)
+        argv.append(self.device)
+        return argv
+
+    def doFormat(self, *args, **kwargs):
+        """ Create the filesystem.
+
+            Arguments:
+
+                None
+
+            Keyword Arguments:
+
+                intf -- InstallInterface instance
+                options -- list of options to pass to mkfs
+
+        """
+        intf = kwargs.get("intf")
+        options = kwargs.get("options")
+
+        if self.exists:
+            raise FormatCreateError("filesystem already exists", self.device)
+
+        if not self.formattable:
+            return
+
+        if not self.mkfsProg:
+            return
+
+        if self.exists:
+            return
+
+        if not os.path.exists(self.device):
+            raise FormatCreateError("device does not exist", self.device)
+
+        argv = self._getFormatOptions(options=options)
+
+        self.installer.window = None
+        self.installer.window = self.installer.intf.progressWindow(_("Formatting"),
+                                                                   _("Creating filesystem on %s...") % (self.device,),
+                                                                   100, pulse = True)
+
+        try:
+            rc = util.execWithPulseProgress(self.mkfsProg,
+                                             argv,
+                                             stdout="/dev/tty5",
+                                             stderr="/dev/tty5",
+                                             progress=w)
+        except Exception as e:
+            raise FormatCreateError(e, self.device)
+        finally:
+            if self.installer.window:
+                self.installer.window.pop()
+
+        if rc:
+            raise FormatCreateError("format failed: %s" % rc, self.device)
+
+        self.exists = True
+        self.notifyKernel()
+
+    def doMigrate(self, intf=None):
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if not self.migratable or not self.migrate:
+            return
+
+        if not os.path.exists(self.device):
+            raise FSError("device does not exist")
+
+        # if journal already exists skip
+        if isys.ext2HasJournal(self.device):
+            self.installer.log.info("Skipping migration of %s, has a journal already." % self.device)
+            return
+
+        argv = self._defaultMigrateOptions[:]
+        argv.append(self.device)
+        try:
+            rc = util.execWithRedirect(self.migratefsProg,
+                                       argv,
+                                       stdout = "/dev/tty5",
+                                       stderr = "/dev/tty5",
+                                       searchPath = 1)
+        except Exception as e:
+            raise FSMigrateError("filesystem migration failed: %s" % e, self.device)
+
+        if rc:
+            raise FSMigrateError("filesystem migration failed: %s" % rc, self.device)
+
+        # the other option is to actually replace this instance with an
+        # instance of the new filesystem type.
+        self._type = self.migrationTarget
+
+    @property
+    def resizeArgs(self):
+        argv = [self.device, "%d" % (self.targetSize,)]
+        return argv
+
+    def doResize(self, *args, **kwargs):
+        """ Resize this filesystem to new size @newsize.
+
+            Arguments:
+
+                None
+
+            Keyword Arguments:
+
+                intf -- InstallInterface instance
+
+        """
+        intf = kwargs.get("intf")
+
+        if not self.exists:
+            raise FSResizeError("filesystem does not exist", self.device)
+
+        if not self.resizable:
+            raise FSResizeError("filesystem not resizable", self.device)
+
+        if self.targetSize == self.currentSize:
+            return
+
+        if not self.resizefsProg:
+            return
+
+        if not os.path.exists(self.device):
+            raise FSResizeError("device does not exist", self.device)
+
+        self.doCheck(intf=intf)
+
+        w = None
+        if intf:
+            w = intf.progressWindow(_("Resizing"),
+                                    _("Resizing filesystem on %s...")
+                                    % (self.device,),
+                                    100, pulse = True)
+
+        try:
+            rc = util.execWithPulseProgress(self.resizefsProg,
+                                            self.resizeArgs,
+                                            stdout="/dev/tty5",
+                                            stderr="/dev/tty5",
+                                            progress=w)
+        except Exception as e:
+            raise FSResizeError(e, self.device)
+        finally:
+            if w:
+                w.pop()
+
+        if rc:
+            raise FSResizeError("resize failed: %s" % rc, self.device)
+
+        # XXX must be a smarter way to do this
+        self._size = self.targetSize
+        self.notifyKernel()
+
+    def _getCheckArgs(self):
+        argv = []
+        argv.extend(self.defaultCheckOptions)
+        argv.append(self.device)
+        return argv
+
+    def doCheck(self, intf=None):
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if not self.fsckProg:
+            return
+
+        if not os.path.exists(self.device):
+            raise FSError("device does not exist")
+
+        w = None
+        if intf:
+            w = intf.progressWindow(_("Checking"),
+                                    _("Checking filesystem on %s...")
+                                    % (self.device),
+                                    100, pulse = True)
+
+        try:
+            rc = util.execWithPulseProgress(self.fsckProg,
+                                            self._getCheckArgs(),
+                                            stdout="/dev/tty5",
+                                            stderr="/dev/tty5",
+                                            progress = w)
+        except Exception as e:
+            raise FSError("filesystem check failed: %s" % e)
+        finally:
+            if w:
+                w.pop()
+
+        if rc >= 4:
+            raise FSError("filesystem check failed: %s" % rc)
+
+    def loadModule(self):
+        """Load whatever kernel module is required to support this filesystem."""
+        global kernel_filesystems
+
+        if not self._modules or self.mountType in kernel_filesystems:
+            return
+
+        for module in self._modules:
+            try:
+                rc = util.execWithRedirect("modprobe", [module],
+                                           stdout="/dev/tty5", stderr="/dev/tty5",
+                                           searchPath=1)
+            except Exception as e:
+                self.installer.log.error("Could not load kernel module %s: %s" % (module, e))
+                self._supported = False
+                return
+
+            if rc:
+                self.installer.log.error("Could not load kernel module %s" % module)
+                self._supported = False
+                return
+
+        # If we successfully loaded a kernel module, for this filesystem, we
+        # also need to update the list of supported filesystems.
+        kernel_filesystems = get_kernel_filesystems()
+
+    def mount(self, *args, **kwargs):
+        """ Mount this filesystem.
+
+            Arguments:
+
+                None
+
+            Keyword Arguments:
+
+                options -- mount options (overrides all other option strings)
+                chroot -- prefix to apply to mountpoint
+                mountpoint -- mountpoint (overrides self.mountpoint)
+        """
+        options = kwargs.get("options", "")
+        chroot = kwargs.get("chroot", "/")
+        mountpoint = kwargs.get("mountpoint")
+
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if not mountpoint:
+            mountpoint = self.mountpoint
+
+        if not mountpoint:
+            raise FSError("no mountpoint given")
+
+        if self.status:
+            return
+
+        if not isinstance(self, NoDevFS) and not os.path.exists(self.device):
+            raise FSError("device %s does not exist" % self.device)
+
+        # XXX os.path.join is FUBAR:
+        #
+        #         os.path.join("/mnt/foo", "/") -> "/"
+        #
+        #mountpoint = os.path.join(chroot, mountpoint)
+        mountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint))
+        util.mkdirChain(mountpoint)
+
+        # passed in options override default options
+        if not options or not isinstance(options, str):
+            options = self.options
+
+        try:
+            rc = isys.mount(self.device, mountpoint,
+                            fstype=self.mountType,
+                            options=options,
+                            bindMount=isinstance(self, BindFS))
+        except Exception as e:
+            raise FSError("mount failed: %s" % e)
+
+        if rc:
+            raise FSError("mount failed: %s" % rc)
+
+        self._mountpoint = mountpoint
+
+    def unmount(self):
+        """ Unmount this filesystem. """
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if not self._mountpoint:
+            # not mounted
+            return
+
+        if not os.path.exists(self._mountpoint):
+            raise FSError("mountpoint does not exist")
+
+        rc = isys.umount(self._mountpoint, removeDir = False)
+        if rc:
+            raise FSError("umount failed")
+
+        self._mountpoint = None
+
+    def _getLabelArgs(self, label):
+        argv = []
+        argv.extend(self.defaultLabelOptions)
+        argv.extend([self.device, label])
+        return argv
+
+    def writeLabel(self, label):
+        """ Create a label for this filesystem. """
+        if not self.exists:
+            raise FSError("filesystem has not been created")
+
+        if not self.labelfsProg:
+            return
+
+        if not os.path.exists(self.device):
+            raise FSError("device does not exist")
+
+        argv = self._getLabelArgs(label)
+        rc = util.execWithRedirect(self.labelfsProg,
+                                   argv,
+                                   stderr="/dev/tty5",
+                                   searchPath=1)
+        if rc:
+            raise FSError("label failed")
+
+        self.label = label
+        self.notifyKernel()
+
+    @property
+    def isDirty(self):
+        return False
+
+    @property
+    def mkfsProg(self):
+        """ Program used to create filesystems of this type. """
+        return self._mkfs
+
+    @property
+    def fsckProg(self):
+        """ Program used to check filesystems of this type. """
+        return self._fsck
+
+    @property
+    def resizefsProg(self):
+        """ Program used to resize filesystems of this type. """
+        return self._resizefs
+
+    @property
+    def labelfsProg(self):
+        """ Program used to manage labels for this filesystem type. """
+        return self._labelfs
+
+    @property
+    def migratefsProg(self):
+        """ Program used to migrate filesystems of this type. """
+        return self._migratefs
+
+    @property
+    def migrationTarget(self):
+        return self._migrationTarget
+
+    @property
+    def utilsAvailable(self):
+        # we aren't checking for fsck because we shouldn't need it
+        for prog in [self.mkfsProg, self.resizefsProg, self.labelfsProg]:
+            if not prog:
+                continue
+
+            if not filter(lambda d: os.access("%s/%s" % (d, prog), os.X_OK),
+                          os.environ["PATH"].split(":")):
+                return False
+
+        return True
+
+    @property
+    def supported(self):
+        return self._supported and self.utilsAvailable
+
+    @property
+    def mountable(self):
+        return (self.mountType in kernel_filesystems) or \
+               (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK))
+
+    @property
+    def defaultFormatOptions(self):
+        """ Default options passed to mkfs for this filesystem type. """
+        # return a copy to prevent modification
+        return self._defaultFormatOptions[:]
+
+    @property
+    def defaultMountOptions(self):
+        """ Default options passed to mount for this filesystem type. """
+        # return a copy to prevent modification
+        return self._defaultMountOptions[:]
+
+    @property
+    def defaultLabelOptions(self):
+        """ Default options passed to labeler for this filesystem type. """
+        # return a copy to prevent modification
+        return self._defaultLabelOptions[:]
+
+    @property
+    def defaultCheckOptions(self):
+        """ Default options passed to checker for this filesystem type. """
+        # return a copy to prevent modification
+        return self._defaultCheckOptions[:]
+
+    def _getOptions(self):
+        options = ",".join(self.defaultMountOptions)
+        if self.mountopts:
+            # XXX should we clobber or append?
+            options = self.mountopts
+        return options
+
+    def _setOptions(self, options):
+        self.mountopts = options
+
+    options = property(_getOptions, _setOptions)
+
+    @property
+    def migratable(self):
+        """ Can filesystems of this type be migrated? """
+        return bool(self._migratable and self.migratefsProg and
+                    filter(lambda d: os.access("%s/%s"
+                                               % (d, self.migratefsProg,),
+                                               os.X_OK),
+                           os.environ["PATH"].split(":")) and
+                    self.migrationTarget)
+
+    def _setMigrate(self, migrate):
+        if not migrate:
+            self._migrate = migrate
+            return
+
+        if self.migratable and self.exists:
+            self._migrate = migrate
+        else:
+            raise ValueError("Cannot set migrate on non-migratable filesystem")
+
+    migrate = property(lambda f: f._migrate, lambda f,m: f._setMigrate(m))
+
+    @property
+    def type(self):
+        _type = self._type
+        if self.migrate:
+            _type = self.migrationTarget
+
+        return _type
+
+    @property
+    def mountType(self):
+        if not self._mountType:
+            self._mountType = self._type
+
+        return self._mountType
+
+    # These methods just wrap filesystem-specific methods in more
+    # generically named methods so filesystems and formatted devices
+    # like swap and LVM physical volumes can have a common API.
+    def create(self, *args, **kwargs):
+        if self.exists:
+            raise FSError("Filesystem already exists")
+
+        DeviceFormat.create(self, *args, **kwargs)
+
+        return self.doFormat(*args, **kwargs)
+
+    def setup(self, *args, **kwargs):
+        """ Mount the filesystem.
+
+            THe filesystem will be mounted at the directory indicated by
+            self.mountpoint.
+        """
+        return self.mount(**kwargs)
+
+    def teardown(self, *args, **kwargs):
+        return self.unmount(*args, **kwargs)
+
+    @property
+    def status(self):
+        # FIXME check /proc/mounts or similar
+        if not self.exists:
+            return False
+        return self._mountpoint is not None
+
+
+class Ext2FS(FS):
+    """ ext2 filesystem. """
+    _type = "ext2"
+    _mkfs = "mke2fs"
+    _modules = ["ext2"]
+    _resizefs = "resize2fs"
+    _labelfs = "e2label"
+    _fsck = "e2fsck"
+    _formattable = True
+    _supported = True
+    _resizable = True
+    _bootable = True
+    _linuxNative = True
+    _maxSize = 8 * 1024 * 1024
+    _minSize = 0
+    _defaultFormatOptions = []
+    _defaultMountOptions = ["defaults"]
+    _defaultCheckOptions = ["-f", "-p", "-C", "0"]
+    _dump = True
+    _check = True
+    _migratable = True
+    _migrationTarget = "ext3"
+    _migratefs = "tune2fs"
+    _defaultMigrateOptions = ["-j"]
+
+    @property
+    def minSize(self):
+        """ Minimum size for this filesystem in MB. """
+        size = self._minSize
+        if self.exists and os.path.exists(self.device):
+            buf = util.execWithCapture(self.resizefsProg,
+                                        ["-P", self.device],
+                                        stderr="/dev/tty5")
+            size = None
+            for line in buf.splitlines():
+                if "minimum size of the filesystem:" not in line:
+                    continue
+
+                (text, sep, minSize) = line.partition(": ")
+
+                size = int(minSize) / 1024.0
+
+            if size is None:
+                self.installer.log.warning("failed to get minimum size for %s filesystem "
+                            "on %s" % (self.mountType, self.device))
+                size = self._minSize
+
+        return size
+
+    @property
+    def isDirty(self):
+        return isys.ext2IsDirty(self.device)
+
+    @property
+    def resizeArgs(self):
+        argv = ["-p", self.device, "%dM" % (self.targetSize,)]
+        return argv
+
+register_device_format(Ext2FS)
+
+
+class Ext3FS(Ext2FS):
+    """ ext3 filesystem. """
+    _type = "ext3"
+    _defaultFormatOptions = ["-t", "ext3"]
+    _migrationTarget = "ext4"
+    _modules = ["ext3"]
+    _defaultMigrateOptions = ["-O", "extents"]
+
+    @property
+    def migratable(self):
+        """ Can filesystems of this type be migrated? """
+        return (flags.cmdline.has_key("ext4migrate") and
+                Ext2FS.migratable)
+
+register_device_format(Ext3FS)
+
+
+class Ext4FS(Ext3FS):
+    """ ext4 filesystem. """
+    _type = "ext4"
+    _bootable = False
+    _defaultFormatOptions = ["-t", "ext4"]
+    _migratable = False
+    _modules = ["ext4"]
+
+register_device_format(Ext4FS)
+
+
+class FATFS(FS):
+    """ FAT filesystem. """
+    _type = "vfat"
+    _mkfs = "mkdosfs"
+    _modules = ["vfat"]
+    _labelfs = "dosfslabel"
+    _fsck = "dosfsck"
+    _formattable = True
+    _maxSize = 1024 * 1024
+    _defaultMountOptions = ["umask=0077", "shortname=winnt"]
+
+register_device_format(FATFS)
+
+
+class BTRFS(FS):
+    """ btrfs filesystem """
+    _type = "btrfs"
+    _mkfs = "mkfs.btrfs"
+    _modules = ["btrfs"]
+    _resizefs = "btrfsctl"
+    _formattable = True
+    _linuxNative = True
+    _bootable = False
+    _maxLabelChars = 256
+    _supported = True
+    _dump = True
+    _check = True
+    _maxSize = 16 * 1024 * 1024
+
+    def _getFormatOptions(self, options=None):
+        argv = []
+        if options and isinstance(options, list):
+            argv.extend(options)
+        argv.extend(self.defaultFormatOptions)
+        if self.label:
+            argv.extend(["-L", self.label])
+        argv.append(self.device)
+        return argv
+
+    @property
+    def resizeArgs(self):
+        argv = ["-r", "%dm" % (self.targetSize,), self.device]
+        return argv
+
+register_device_format(BTRFS)
+
+class XFS(FS):
+    """ XFS filesystem """
+    _type = "xfs"
+    _mkfs = "mkfs.xfs"
+    _modules = ["xfs"]
+    _labelfs = "xfs_admin"
+    _defaultFormatOptions = ["-f"]
+    _defaultLabelOptions = ["-L"]
+    _maxLabelChars = 16
+    _maxSize = 16 * 1024 * 1024
+    _formattable = True
+    _linuxNative = True
+    _supported = True
+    _dump = True
+    _check = True
+
+register_device_format(XFS)
+
+class NTFS(FS):
+    """ ntfs filesystem. """
+    _type = "ntfs"
+    _resizefs = "ntfsresize"
+    _fsck = "ntfsresize"
+    _resizable = True
+    _minSize = 1
+    _maxSize = 16 * 1024 * 1024
+    _defaultMountOptions = ["defaults"]
+    _defaultCheckOptions = ["-c"]
+
+    @property
+    def minSize(self):
+        """ The minimum filesystem size in megabytes. """
+        size = self._minSize
+        if self.exists and os.path.exists(self.device):
+            minSize = None
+            buf = util.execWithCapture(self.resizefsProg,
+                                       ["-m", self.device],
+                                       stderr = "/dev/tty5")
+            for l in buf.split("\n"):
+                if not l.startswith("Minsize"):
+                    continue
+                try:
+                    min = l.split(":")[1].strip()
+                    minSize = int(min) + 250
+                except Exception, e:
+                    minSize = None
+                    self.installer.log.warning("Unable to parse output for minimum size on %s: %s" %(self.device, e))
+
+            if minSize is None:
+                self.installer.log.warning("Unable to discover minimum size of filesystem "
+                            "on %s" %(self.device,))
+            else:
+                size = minSize
+
+        return size
+
+    @property
+    def resizeArgs(self):
+        # You must supply at least two '-f' options to ntfsresize or
+        # the proceed question will be presented to you.
+        argv = ["-ff", "-s", "%dM" % (self.targetSize,), self.device]
+        return argv
+
+register_device_format(NTFS)
+
+
+# if this isn't going to be mountable it might as well not be here
+class NFS(FS):
+    """ NFS filesystem. """
+    _type = "nfs"
+    _modules = ["nfs"]
+
+    def _deviceCheck(self, devspec):
+        if devspec is not None and ":" not in devspec:
+            raise ValueError("device must be of the form <host>:<path>")
+
+    @property
+    def mountable(self):
+        return False
+
+    def _setDevice(self, devspec):
+        self._deviceCheck(devspec)
+        self._device = devspec
+
+    def _getDevice(self):
+        return self._device
+
+    device = property(lambda f: f._getDevice(),
+                      lambda f,d: f._setDevice(d),
+                      doc="Full path the device this format occupies")
+
+register_device_format(NFS)
+
+
+class NFSv4(NFS):
+    """ NFSv4 filesystem. """
+    _type = "nfs4"
+    _modules = ["nfs4"]
+
+register_device_format(NFSv4)
+
+
+class Iso9660FS(FS):
+    """ ISO9660 filesystem. """
+    _type = "iso9660"
+    _formattable = False
+    _supported = True
+    _resizable = False
+    _bootable = False
+    _linuxNative = False
+    _dump = False
+    _check = False
+    _migratable = False
+    _defaultMountOptions = ["ro"]
+
+register_device_format(Iso9660FS)
+
+
+class NoDevFS(FS):
+    """ nodev filesystem base class """
+    _type = "nodev"
+
+    def __init__(self, *args, **kwargs):
+        FS.__init__(self, *args, **kwargs)
+        self.exists = True
+        self.device = self.type
+
+    def _setDevice(self, devspec):
+        self._device = devspec
+
+register_device_format(NoDevFS)
+
+
+class DevPtsFS(NoDevFS):
+    """ devpts filesystem. """
+    _type = "devpts"
+    _defaultMountOptions = ["gid=5", "mode=620"]
+
+register_device_format(DevPtsFS)
+
+
+# these don't really need to be here
+class ProcFS(NoDevFS):
+    _type = "proc"
+
+register_device_format(ProcFS)
+
+
+class SysFS(NoDevFS):
+    _type = "sysfs"
+
+register_device_format(SysFS)
+
+
+class TmpFS(NoDevFS):
+    _type = "tmpfs"
+
+register_device_format(TmpFS)
+
+
+class BindFS(FS):
+    _type = "bind"
+
+    @property
+    def mountable(self):
+        return True
+
+register_device_format(BindFS)
diff --git a/pkgs/core/pomona/src/storage_old/formats/luks.py b/pkgs/core/pomona/src/storage_old/formats/luks.py
new file mode 100644 (file)
index 0000000..25d9db4
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/python
+
+import os
+
+from ..errors import *
+#from ..devicelibs import crypto
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+class LUKS(DeviceFormat):
+    """ A LUKS device. """
+    _type = "luks"
+    _name = "LUKS"
+    _udevTypes = ["crypto_LUKS"]
+    _formattable = True                 # can be formatted
+    _supported = False                  # is supported
+    _linuxNative = True                 # for clearpart
+
+    def __init__(self, *args, **kwargs):
+        """ Create a LUKS instance.
+
+            Keyword Arguments:
+
+                device -- the path to the underlying device
+                name -- the name of the mapped device
+                uuid -- this device's UUID
+                passphrase -- device passphrase (string)
+                key_file -- path to a file containing a key (string)
+                cipher -- cipher mode string
+                key_size -- key size in bits
+                exists -- indicates whether this is an existing format
+        """
+        DeviceFormat.__init__(self, *args, **kwargs)
+        self.cipher = kwargs.get("cipher")
+        self.key_size = kwargs.get("key_size")
+        self.mapName = kwargs.get("name")
+
+        if not self.exists and not self.cipher:
+            self.cipher = "aes-xts-plain"
+            if not self.key_size:
+                # default to the max (512 bits) for aes-xts
+                self.key_size = 512
+
+        # FIXME: these should both be lists, but managing them will be a pain
+        self.__passphrase = kwargs.get("passphrase")
+        self._key_file = kwargs.get("key_file")
+
+        if not self.mapName and self.exists and self.uuid:
+            self.mapName = "luks-%s" % self.uuid
+        elif not self.mapName and self.device:
+            self.mapName = "luks-%s" % os.path.basename(self.device)
+
+    def _setPassphrase(self, passphrase):
+        """ Set the passphrase used to access this device. """
+        self.__passphrase = passphrase
+
+    passphrase = property(fset=_setPassphrase)
+
+    @property
+    def hasKey(self):
+        return (self.__passphrase or
+                (self._key_file and os.access(self._key_file, os.R_OK)))
+
+    @property
+    def configured(self):
+        """ To be ready we need a key or passphrase and a map name. """
+        return self.hasKey and self.mapName
+
+    @property
+    def status(self):
+        if not self.exists or not self.mapName:
+            return False
+        return os.path.exists("/dev/mapper/%s" % self.mapName)
+
+    def probe(self):
+        """ Probe for any missing information about this format.
+
+            cipher mode, key size
+        """
+        raise NotImplementedError("Probe method not defined for LUKS")
+
+    def setup(self, *args, **kwargs):
+        """ Open, or set up, the format. """
+        if not self.configured:
+            raise LUKSError("luks device not configured")
+
+        if self.status:
+            return
+
+        DeviceFormat.setup(self, *args, **kwargs)
+        crypto.luks_open(self.device, self.mapName,
+                       passphrase=self.__passphrase,
+                       key_file=self._key_file)
+
+    def teardown(self, *args, **kwargs):
+        """ Close, or tear down, the format. """
+        if not self.exists:
+            raise LUKSError("format has not been created")
+
+        if self.status:
+            log.debug("unmapping %s" % self.mapName)
+            crypto.luks_close(self.mapName)
+
+    def create(self, *args, **kwargs):
+        """ Create the format. """
+        if not self.hasKey:
+            raise LUKSError("luks device has no key/passphrase")
+
+        DeviceFormat.create(self, *args, **kwargs)
+        crypto.luks_format(self.device,
+                         passphrase=self.__passphrase,
+                         key_file=self._key_file,
+                         cipher=self.cipher,
+                         key_size=self.key_size)
+
+        self.uuid = crypto.luks_uuid(self.device)
+        self.exists = True
+        self.mapName = "luks-%s" % self.uuid
+        self.notifyKernel()
+
+    def destroy(self, *args, **kwargs):
+        """ Create the format. """
+        self.teardown()
+        DeviceFormat.destroy(self, *args, **kwargs)
+
+    @property
+    def keyFile(self):
+        """ Path to key file to be used in /etc/crypttab """
+        return self._key_file
+
+    def addKeyFromFile(self, keyfile):
+        """ Add a new key from a file.
+
+            Add the contents of the specified key file to an available key
+            slot in the LUKS header.
+        """
+        if not self.exists:
+            raise LUKSError("Format has not been created")
+
+        crypto.luks_add_key(self.device,
+                          passphrase=self.__passphrase,
+                          key_file=self._key_file,
+                          new_key_file=keyfile)
+
+    def addPassphrase(self, passphrase):
+        """ Add a new passphrase.
+
+            Add the specified passphrase to an available key slot in the
+            LUKS header.
+        """
+        if not self.exists:
+            raise LUKSError("Format has not been created")
+
+        crypto.luks_add_key(self.device,
+                          passphrase=self.__passphrase,
+                          key_file=self._key_file,
+                          new_passphrase=passphrase)
+
+    def removeKeyFromFile(self, keyfile):
+        """ Remove a key contained in a file.
+
+            Remove key contained in the specified key file from the LUKS
+            header.
+        """
+        if not self.exists:
+            raise LUKSError("Format has not been created")
+
+        crypto.luks_remove_key(self.device,
+                             passphrase=self.__passphrase,
+                             key_file=self._key_file,
+                             del_key_file=keyfile)
+
+
+    def removePassphrase(self, passphrase):
+        """ Remove the specified passphrase from the LUKS header. """
+        if not self.exists:
+            raise LUKSError("Format has not been created")
+
+        crypto.luks_remove_key(self.device,
+                             passphrase=self.__passphrase,
+                             key_file=self._key_file,
+                             del_passphrase=passphrase)
+
+
+register_device_format(LUKS)
diff --git a/pkgs/core/pomona/src/storage_old/formats/lvmpv.py b/pkgs/core/pomona/src/storage_old/formats/lvmpv.py
new file mode 100644 (file)
index 0000000..cccff87
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+
+from parted import PARTITION_LVM
+
+from . import DeviceFormat, register_device_format
+from ..errors import *
+from ..devicelibs import lvm
+
+class LVMPhysicalVolume(DeviceFormat):
+    """ An LVM physical volume. """
+    _type = "lvmpv"
+    _name = "physical volume (LVM)"
+    _udevTypes = ["LVM2_member"]
+    partedFlag = PARTITION_LVM
+    _formattable = True                 # can be formatted
+    _supported = True                   # is supported
+    _linuxNative = True                 # for clearpart
+
+    def __init__(self, *args, **kwargs):
+        """ Create an LVMPhysicalVolume instance.
+
+            Keyword Arguments:
+
+                device -- path to the underlying device
+                uuid -- this PV's uuid (not the VG uuid)
+                vgName -- the name of the VG this PV belongs to
+                vgUuid -- the UUID of the VG this PV belongs to
+                peStart -- offset of first physical extent
+                exists -- indicates whether this is an existing format
+
+        """
+        DeviceFormat.__init__(self, *args, **kwargs)
+        self.vgName = kwargs.get("vgName")
+        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", 0.1875)    # in MB
+
+    def probe(self):
+        """ Probe for any missing information about this device. """
+        if not self.exists:
+            raise PhysicalVolumeError("format has not been created")
+
+        #info = lvm.pvinfo(self.device)
+        #self.vgName = info['vg_name']
+        #self.vgUuid = info['vg_uuid']
+
+    def create(self, *args, **kwargs):
+        """ Create the format. """
+        DeviceFormat.create(self, *args, **kwargs)
+        # Consider use of -Z|--zero
+        # -f|--force or -y|--yes may be required
+
+        # lvm has issues with persistence of metadata, so here comes the
+        # hammer...
+        DeviceFormat.destroy(self, *args, **kwargs)
+
+        lvm.pvcreate(self.device)
+        self.exists = True
+        self.notifyKernel()
+
+    def destroy(self, *args, **kwargs):
+        """ Destroy the format. """
+        if not self.exists:
+            raise PhysicalVolumeError("format has not been created")
+
+        if self.status:
+            raise PhysicalVolumeError("device is active")
+
+        # FIXME: verify path exists?
+        try:
+            lvm.pvremove(self.device)
+        except LVMError:
+            DeviceFormat.destroy(self, *args, **kwargs)
+
+        self.exists = False
+        self.notifyKernel()
+
+    @property
+    def status(self):
+        # XXX hack
+        return (self.exists and self.vgName and
+                os.path.isdir("/dev/mapper/%s" % self.vgName))
+
+register_device_format(LVMPhysicalVolume)
diff --git a/pkgs/core/pomona/src/storage_old/formats/swap.py b/pkgs/core/pomona/src/storage_old/formats/swap.py
new file mode 100644 (file)
index 0000000..487d365
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/python
+
+from parted import PARTITION_SWAP
+
+from . import DeviceFormat, register_device_format
+from ..devicelibs import swap
+
+class SwapSpace(DeviceFormat):
+    """ Swap space """
+    _type = "swap"
+    _name = None
+    _udevTypes = ["swap"]
+    partedFlag = PARTITION_SWAP
+    _formattable = True                # can be formatted
+    _supported = True                  # is supported
+    _linuxNative = True                # for clearpart
+
+    def __init__(self, installer, *args, **kwargs):
+        """ Create a SwapSpace instance.
+
+            Keyword Arguments:
+
+                device -- path to the underlying device
+                uuid -- this swap space's uuid
+                label -- this swap space's label
+                priority -- this swap space's priority
+                exists -- indicates whether this is an existing format
+
+        """
+        self.installer = installer
+        DeviceFormat.__init__(self, self.installer, *args, **kwargs)
+
+        self.priority = kwargs.get("priority")
+        self.label = kwargs.get("label")
+
+    def _setPriority(self, priority):
+        if priority is None:
+            self._priority = None
+            return
+
+        if not isinstance(priority, int) or not 0 <= priority <= 32767:
+            raise ValueError("swap priority must be an integer between 0 and 32767")
+
+        self._priority = priority
+
+    def _getPriority(self):
+        return self._priority
+
+    priority = property(_getPriority, _setPriority,
+                        doc="The priority of the swap device")
+
+    def _getOptions(self):
+        opts = ""
+        if self.priority is not None:
+            opts += "pri=%d" % self.priority
+
+        return opts
+
+    def _setOptions(self, opts):
+        if not opts:
+            self.priority = None
+            return
+
+        for option in opts.split(","):
+            (opt, equals, arg) = option.partition("=")
+            if equals and opt == "pri":
+                try:
+                    self.priority = int(arg)
+                except ValueError:
+                    self.installer.log.info("invalid value for swap priority: %s" % arg)
+
+    options = property(_getOptions, _setOptions,
+                       doc="The swap device's fstab options string")
+
+    @property
+    def status(self):
+        """ Device status. """
+        return self.exists and swap.swapstatus(self.device)
+
+    def setup(self, *args, **kwargs):
+        """ Open, or set up, a device. """
+        if not self.exists:
+            raise SwapSpaceError("format has not been created")
+
+        if self.status:
+            return
+
+        DeviceFormat.setup(self, *args, **kwargs)
+        swap.swapon(self.device, priority=self.priority)
+
+    def teardown(self, *args, **kwargs):
+        """ Close, or tear down, a device. """
+        if not self.exists:
+            raise SwapSpaceError("format has not been created")
+
+        if self.status:
+            swap.swapoff(self.device)
+
+    def create(self, *args, **kwargs):
+        """ Create the device. """
+        if self.exists:
+            raise SwapSpaceError("format already exists")
+
+        if self.status:
+            raise SwapSpaceError("device exists and is active")
+
+        DeviceFormat.create(self, *args, **kwargs)
+        swap.mkswap(self.device, label=self.label)
+        self.exists = True
+
+
+register_device_format(SwapSpace)
diff --git a/pkgs/core/pomona/src/storage_old/partitioning.py b/pkgs/core/pomona/src/storage_old/partitioning.py
new file mode 100644 (file)
index 0000000..d7672a6
--- /dev/null
@@ -0,0 +1,1059 @@
+#!/usr/bin/python
+
+import os
+import parted
+
+from constants import *
+from errors import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+def doAutoPartition(installer):
+    if installer.dispatch.dir == DISPATCH_BACK:
+        installer.ds.storage.reset()
+        return
+
+    disks = []
+    devs = []
+
+    if installer.ds.storage.doAutoPart:
+        clearPartitions(installer)
+        (disks, devs) = _createFreeSpacePartitions(installer)
+
+        if disks == []:
+            installer.intf.messageWindow(_("Error Partitioning"),
+                                        _("Could not find enough free space "
+                                          "for automatic partitioning, please "
+                                          "use another partitioning method."))
+            return DISPATCH_BACK
+
+        _schedulePartitions(installer, disks)
+
+    # run the autopart function to allocate and grow partitions
+    try:
+        doPartitioning(installer)
+
+        if installer.ds.storage.doAutoPart:
+            _scheduleLVs(installer, devs)
+
+        # grow LVs
+        growLVM(installer)
+    except PartitioningWarning as msg:
+        installer.intf.messageWindow(_("Warnings During Automatic Partitioning"),
+                                     _("Following warnings occurred during automatic "
+                                       "partitioning:\n\n%s") % (msg,),)
+        log.warning(msg)
+    except PartitioningError as msg:
+        # restore drives to original state
+        installer.ds.storage.reset()
+        #installer.dispatch.skipStep("partition", skip = 0)
+        installer.intf.messageWindow(_("Error Partitioning"),
+               _("Could not allocate requested partitions: \n\n"
+                 "%s.%s") % (msg, extra))
+        return
+
+    # now do a full check of the requests
+    (errors, warnings) = installer.ds.storage.sanityCheck()
+    if warnings:
+        for warning in warnings:
+            installer.log.warning(warning)
+    if errors:
+        errortxt = "\n".join(errors)
+        installer.intf.messageWindow(_("Automatic Partitioning Errors"),
+                           _("The following errors occurred with your "
+                             "partitioning:\n\n%s\n\n"
+                             "This can happen if there is not enough "
+                             "space on your hard drive(s) for the "
+                             "installation.\n\n"
+                                                         "Press 'OK' to choose a different partitioning option.")
+                           % (errortxt,),)
+
+        installer.ds.storage.reset()
+        #XXX return DISPATCH_BACK
+        return INSTALL_OK
+
+def doPartitioning(installer):
+    """ Allocate and grow partitions.
+
+        When this function returns without error, all PartitionDevice
+        instances must have their parents set to the disk they are
+        allocated on, and their partedPartition attribute set to the
+        appropriate parted.Partition instance from their containing
+        disk. All req_xxxx attributes must be unchanged.
+
+        Arguments:
+
+            storage - Main anaconda Storage instance
+
+        Keyword arguments:
+
+            exclusiveDisks -- list of names of disks to use
+
+    """
+    storage = installer.ds.storage
+    disks = storage.disks
+
+    exclusiveDisks = storage.clearDisks
+    if exclusiveDisks:
+        disks = [d for d in disks if d.name in exclusiveDisks]
+
+    for disk in disks:
+        disk.setup()
+
+    partitions = storage.partitions
+    for part in partitions:
+        part.req_bootable = False
+        if not part.exists:
+            # start over with flexible-size requests
+            part.req_size = part.req_base_size
+
+    # FIXME: isn't there a better place for this to happen?
+    #try:
+    #    bootDev = anaconda.platform.bootDevice()
+    #except DeviceError:
+    #    bootDev = None
+
+    #if bootDev:
+    #    bootDev.req_bootable = True
+
+    # FIXME: make sure non-existent partitions have empty parents list
+    allocatePartitions(installer, disks, partitions)
+    growPartitions(installer, disks, partitions)
+    # The number and thus the name of partitions may have changed now,
+    # allocatePartitions() takes care of this for new partitions, but not
+    # for pre-existing ones, so we update the name of all partitions here
+    for part in partitions:
+        part.updateName()
+
+    # XXX hack -- if we created any extended partitions we need to add
+    #             them to the tree now
+    for disk in disks:
+        extended = disk.partedDisk.getExtendedPartition()
+        if not extended:
+            continue
+
+        extendedName = devicePathToName(extended.getDeviceNodeName())
+        device = storage.devicetree.getDeviceByName(extendedName)
+        if device:
+            if not device.exists:
+                # created by us, update partedPartition
+                device.partedPartition = extended
+            continue
+
+        # This is a little odd because normally instantiating a partition
+        # that does not exist means leaving self.parents empty and instead
+        # populating self.req_disks. In this case, we need to skip past
+        # that since this partition is already defined.
+        device = PartitionDevice(extendedName, parents=disk)
+        device.parents = [disk]
+        device.partedPartition = extended
+        storage.createDevice(device)
+
+def clearPartitions(installer):
+    """ Clear partitions and dependent devices from disks.
+
+        Arguments:
+
+            storage -- a storage.Storage instance
+
+        Keyword arguments:
+
+            None
+
+        NOTES:
+
+            - Needs some error handling, especially for the parted bits.
+
+    """
+    storage = installer.ds.storage
+
+    # we are only interested in partitions that physically exist
+    partitions = [p for p in storage.partitions if p.exists]
+    disks = []  # a list of disks from which we've removed partitions
+    clearparts = [] # list of partitions we'll remove
+    for part in partitions:
+        # if we got a list of disks to clear, make sure this one's on it
+        if storage.clearDisks and part.disk.name not in storage.clearDisks:
+            continue
+
+        # don't clear partitions holding install media
+        #if part.name in storage.protectedPartitions:
+        #    continue
+
+        # we don't want to fool with extended partitions, freespace
+        if part.partType not in (parted.PARTITION_NORMAL, parted.PARTITION_LOGICAL):
+            continue
+
+        # XXX is there any argument for not removing incomplete devices?
+        #       -- maybe some RAID devices
+        devices = storage.deviceDeps(part)
+        while devices:
+            installer.log.debug("Devices to remove: %s" % ([d.name for d in devices],))
+            leaves = [d for d in devices if d.isleaf]
+            installer.log.debug("Leaves to remove: %s" % ([d.name for d in leaves],))
+            for leaf in leaves:
+                storage.destroyDevice(leaf)
+                devices.remove(leaf)
+
+        #installer.log.debug("Partitions left: %s" % [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
+        disk_name = os.path.basename(part.partedPartition.disk.device.path)
+        if disk_name not in disks:
+            disks.append(disk_name)
+
+        clearparts.append(part)
+
+    for part in clearparts:
+        storage.destroyDevice(part)
+
+    # now remove any empty extended partitions
+    removeEmptyExtendedPartitions(installer)
+
+def removeEmptyExtendedPartitions(installer):
+    storage = installer.ds.storage
+    for disk in storage.disks:
+        #installer.log.debug("Checking whether disk %s has an empty extended" % disk.name)
+        extended = disk.partedDisk.getExtendedPartition()
+        logical_parts = disk.partedDisk.getLogicalPartitions()
+        #installer.log.debug("Extended is %s ; logicals is %s" % (extended, [p.getDeviceNodeName() for p in logical_parts]))
+        if extended and not logical_parts:
+            installer.log.debug("Removing empty extended partition from %s" % disk.name)
+            extended_name = devicePathToName(extended.getDeviceNodeName())
+            extended = storage.devicetree.getDeviceByName(extended_name)
+            storage.destroyDevice(extended)
+            #disk.partedDisk.removePartition(extended.partedPartition)
+
+def _createFreeSpacePartitions(installer):
+    # get a list of disks that have at least one free space region of at
+    # least 100MB
+    disks = []
+    for disk in installer.ds.storage.disks:
+        if disk.name not in installer.ds.storage.clearDisks:
+            continue
+
+        partedDisk = disk.partedDisk
+        part = disk.partedDisk.getFirstPartition()
+        while part:
+            if not part.type & parted.PARTITION_FREESPACE:
+                part = part.nextPartition()
+                continue
+
+            if part.getSize(unit="MB") > 100:
+                disks.append(disk)
+                break
+
+            part = part.nextPartition()
+
+    # create a separate pv partition for each disk with free space
+    devs = []
+    for disk in disks:
+        if installer.ds.storage.encryptedAutoPart:
+            fmt_type = "luks"
+        else:
+            fmt_type = "lvmpv"
+        part = installer.ds.storage.newPartition(fmt_type=fmt_type,
+                                                 size=1, grow=True,
+                                                 disks=[disk])
+        installer.ds.storage.createDevice(part)
+        devs.append(part)
+
+    return (disks, devs)
+
+def _schedulePartitions(installer, disks):
+    #
+    # Convert storage.autoPartitionRequests into Device instances and
+    # schedule them for creation
+    #
+    # First pass is for partitions only. We'll do LVs later.
+    #
+    for request in installer.ds.storage.autoPartitionRequests:
+        if request.asVol:
+            continue
+
+        if request.fstype is None:
+            request.fstype = installer.ds.storage.defaultFSType
+
+        dev = installer.ds.storage.newPartition(fmt_type=request.fstype,
+                                                size=request.size,
+                                                grow=request.grow,
+                                                maxsize=request.maxSize,
+                                                mountpoint=request.mountpoint,
+                                                disks=disks,
+                                                weight=request.weight)
+
+        # schedule the device for creation
+        installer.ds.storage.createDevice(dev)
+
+    # make sure preexisting broken lvm/raid configs get out of the way
+    return
+
+def allocatePartitions(installer, disks, partitions):
+    """ Allocate partitions based on requested features.
+
+        Non-existing partitions are sorted according to their requested
+        attributes, and then allocated.
+
+        The basic approach to sorting is that the more specifically-
+        defined a request is, the earlier it will be allocated. See
+        the function partitionCompare for details on the sorting
+        criteria.
+
+        The PartitionDevice instances will have their name and parents
+        attributes set once they have been allocated.
+    """
+    #installer.log.debug("disks=%s ; partitions=%s" % (disks, partitions))
+    new_partitions = [p for p in partitions if not p.exists]
+    new_partitions.sort(cmp=partitionCompare)
+
+    # XXX is this needed anymore?
+    partedDisks = {}
+    for disk in disks:
+        if disk.path not in partedDisks.keys():
+            partedDisks[disk.path] = disk.partedDisk #.duplicate()
+
+    # remove all newly added partitions from the disk
+    installer.log.debug("Removing all non-preexisting from disk(s)")
+    for _part in new_partitions:
+        if _part.partedPartition:
+            if _part.isExtended:
+                continue # these get removed last
+            #_part.disk.partedDisk.removePartition(_part.partedPartition)
+            partedDisk = partedDisks[_part.disk.partedDisk.device.path]
+            installer.log.debug("Removing part %s (%s) from disk %s (%s)" %
+                      (_part.partedPartition.path,
+                       [p.path for p in _part.partedPartition.disk.partitions],
+                       partedDisk.device.path,
+                       [p.path for p in partedDisk.partitions]))
+
+            partedDisk.removePartition(_part.partedPartition)
+            _part.partedPartition = None
+            _part.disk = None
+
+            # remove empty extended so it doesn't interfere
+            extended = partedDisk.getExtendedPartition()
+            if extended and not partedDisk.getLogicalPartitions():
+                installer.log.debug("Removing empty extended partition")
+                #partedDisk.minimizeExtendedPartition()
+                partedDisk.removePartition(extended)
+
+    for _part in new_partitions:
+        if _part.partedPartition and _part.isExtended:
+            # ignore new extendeds as they are implicit requests
+            continue
+
+        # obtain the set of candidate disks
+        req_disks = []
+        if _part.disk:
+            # we have a already selected a disk for this request
+            req_disks = [_part.disk]
+        elif _part.req_disks:
+            # use the requested disk set
+            req_disks = _part.req_disks
+        else:
+            # no disks specified means any disk will do
+            req_disks = disks
+
+        #installer.log.debug("allocating partition: %s ; disks: %s ; boot: %s ; "
+        #                    "primary: %s ; size: %dMB ; grow: %s ; max_size: %s" %
+        #                    (_part.name, req_disks, _part.req_bootable, _part.req_primary,
+        #                    _part.req_size, _part.req_grow, _part.req_max_size))
+        free = None
+        use_disk = None
+        part_type = None
+        # loop through disks
+        for _disk in req_disks:
+            disk = partedDisks[_disk.path]
+            #for p in disk.partitions:
+            #    installer.log.debug("disk %s: part %s" % (disk.device.path, p.path))
+            sectorSize = disk.device.physicalSectorSize
+            best = None
+
+            #installer.log.debug("Checking freespace on %s" % _disk.name)
+
+            new_part_type = getNextPartitionType(disk)
+            if new_part_type is None:
+                # can't allocate any more partitions on this disk
+                installer.log.debug("No free partition slots on %s" % _disk.name)
+                continue
+
+            if _part.req_primary and new_part_type != parted.PARTITION_NORMAL:
+                # we need a primary slot and none are free on this disk
+                installer.log.debug("No primary slots available on %s" % _disk.name)
+                continue
+
+            best = getBestFreeSpaceRegion(installer, disk,
+                                          new_part_type,
+                                          _part.req_size,
+                                          best_free=free,
+                                          boot=_part.req_bootable)
+
+            if best == free and not _part.req_primary and \
+               new_part_type == parted.PARTITION_NORMAL:
+                # see if we can do better with a logical partition
+                installer.log.debug("Not enough free space for primary -- trying logical")
+                new_part_type = getNextPartitionType(disk, no_primary=True)
+                if new_part_type:
+                    best = getBestFreeSpaceRegion(disk,
+                                                  new_part_type,
+                                                  _part.req_size,
+                                                  best_free=free,
+                                                  boot=_part.req_bootable)
+
+            if best and free != best:
+                # now we know we are choosing a new free space,
+                # so update the disk and part type
+                #installer.log.debug("Updating use_disk to %s (%s), type: %s"
+                #                    % (_disk, _disk.name, new_part_type))
+                part_type = new_part_type
+                use_disk = _disk
+                installer.log.debug("New free: %s (%d-%d / %dMB)" % (best.device.path,
+                                                                     best.start,
+                                                                     best.end,
+                                                                     best.getSize()))
+                free = best
+
+            if free and _part.req_bootable:
+                # if this is a bootable partition we want to
+                # use the first freespace region large enough
+                # to satisfy the request
+                installer.log.debug("Found free space for bootable request")
+                break
+
+        if free is None:
+            raise PartitioningError("Not enough free space on disks")
+
+        _disk = use_disk
+        disk = _disk.partedDisk
+
+        # create the extended partition if needed
+        # TODO: move to a function (disk, free)
+        if part_type == parted.PARTITION_EXTENDED:
+            installer.log.debug("Creating extended partition")
+            geometry = parted.Geometry(device=disk.device,
+                                       start=free.start,
+                                       length=free.length,
+                                       end=free.end)
+            extended = parted.Partition(disk=disk,
+                                        type=parted.PARTITION_EXTENDED,
+                                        geometry=geometry)
+            constraint = parted.Constraint(device=disk.device)
+            # FIXME: we should add this to the tree as well
+            disk.addPartition(extended, constraint)
+
+            # end proposed function
+
+            # now the extended partition exists, so set type to logical
+            part_type = parted.PARTITION_LOGICAL
+
+            # recalculate freespace
+            installer.log.debug("Recalculating free space")
+            free = getBestFreeSpaceRegion(disk,
+                                          part_type,
+                                          _part.req_size,
+                                          boot=_part.req_bootable)
+            if not free:
+                raise PartitioningError("Not enough free space after "
+                                        "creating extended partition")
+
+        # create minimum geometry for this request
+        # req_size is in MB
+        sectors_per_track = disk.device.biosGeometry[2]
+        length = (_part.req_size * (1024 * 1024)) / sectorSize
+        new_geom = parted.Geometry(device=disk.device,
+                                   start=max(sectors_per_track, free.start),
+                                   length=length)
+
+        # create maximum and minimum geometries for constraint
+        start = max(0 , free.start - 1)
+        max_geom = parted.Geometry(device=disk.device,
+                                   start=start,
+                                   length=min(length + 1, disk.device.length - start))
+        min_geom = parted.Geometry(device=disk.device,
+                                   start=free.start + 1,
+                                   length=length-1)
+
+
+        # create the partition and add it to the disk
+        partition = parted.Partition(disk=disk,
+                                     type=part_type,
+                                     geometry=new_geom)
+        constraint = parted.Constraint(maxGeom=max_geom, minGeom=min_geom)
+        disk.addPartition(partition=partition,
+                          constraint=constraint)
+        installer.log.debug("Created partition %s of %dMB and added it to %s" %
+                (partition.getDeviceNodeName(), partition.getSize(), disk.device.path))
+
+        # this one sets the name
+        _part.partedPartition = partition
+        _part.disk = _disk
+
+        # parted modifies the partition in the process of adding it to
+        # the disk, so we need to grab the latest version...
+        _part.partedPartition = disk.getPartitionByPath(_part.path)
+
+def growPartitions(installer, disks, partitions):
+    """ Grow all growable partition requests.
+
+        All requests should know what disk they will be on by the time
+        this function is called. This is reflected in the
+        PartitionDevice's disk attribute. Note that the req_disks
+        attribute remains unchanged.
+
+        The total available free space is summed up for each disk and
+        partition requests are allocated a maximum percentage of the
+        available free space on their disk based on their own base size.
+
+        Each attempted size means calling allocatePartitions again with
+        one request's size having changed.
+
+        After taking into account several factors that may limit the
+        maximum size of a requested partition, we arrive at a firm
+        maximum number of sectors by which a request can potentially grow.
+
+        An initial attempt is made to allocate the full maximum size. If
+        this fails, we begin a rough binary search with a maximum of three
+        iterations to settle on a new size.
+
+        Arguments:
+
+            disks -- a list of all usable disks (DiskDevice instances)
+            partitions -- a list of all partitions (PartitionDevice
+                          instances)
+    """
+    #installer.log.debug("growPartitions: disks=%s, partitions=%s" %
+    #            ([d.name for d in disks], [p.name for p in partitions]))
+    all_growable = [p for p in partitions if p.req_grow]
+    if not all_growable:
+        return
+
+    # sort requests by base size in decreasing order
+    all_growable.sort(key=lambda p: p.req_size, reverse=True)
+
+    installer.log.debug("Growable requests are %s" % [p.name for p in all_growable])
+
+    for disk in disks:
+        installer.log.debug("Growing requests on %s" % disk.name)
+        for p in disk.partedDisk.partitions:
+            installer.log.debug("  %s: %s (%dMB)" % (disk.name, p.getDeviceNodeName(),
+                                         p.getSize()))
+        sectorSize = disk.partedDisk.device.physicalSectorSize
+        # get a list of free space regions on the disk
+        free = disk.partedDisk.getFreeSpaceRegions()
+        if not free:
+            installer.log.debug("No free space on %s" % disk.name)
+            continue
+
+        # sort the free regions in decreasing order of size
+        free.sort(key=lambda r: r.length, reverse=True)
+        disk_free = reduce(lambda x,y: x + y, [f.length for f in free])
+        installer.log.debug("Total free: %d sectors ; largest: %d sectors (%dMB)"
+                    % (disk_free, free[0].length, free[0].getSize()))
+
+        # make a list of partitions currently allocated on this disk
+        # -- they're already sorted
+        growable = []
+        disk_total = 0
+        for part in all_growable:
+            #log.debug("checking if part %s (%s) is on this disk" % (part.name,
+            #                                                        part.disk.name))
+            if part.disk == disk:
+                growable.append(part)
+                disk_total += part.partedPartition.geometry.length
+                installer.log.debug("Add %s (%dMB/%d sectors) to growable total"
+                            % (part.name, part.partedPartition.getSize(),
+                                part.partedPartition.geometry.length))
+                installer.log.debug("Growable total is now %d sectors" % disk_total)
+
+        # now we loop through the partitions...
+        # this first loop is to identify obvious chunks of free space that
+        # will be left over due to max size
+        leftover = 0
+        limited = {}
+        unlimited_total = 0
+        for part in growable:
+            # calculate max number of sectors this request can grow
+            req_sectors = part.partedPartition.geometry.length
+            share = float(req_sectors) / float(disk_total)
+            max_grow = (share * disk_free)
+            max_sectors = req_sectors + max_grow
+            limited[part.name] = False
+
+            if part.req_max_size:
+                req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
+                if req_max_sect < max_sectors:
+                    mb = ((max_sectors - req_max_sect) * sectorSize) / (1024*1024)
+
+                    installer.log.debug("Adding %dMB to leftovers from %s"
+                                % (mb, part.name))
+                    leftover += (max_sectors - req_max_sect)
+                    limited[part.name] = True
+
+            if not limited[part.name]:
+                unlimited_total += req_sectors
+
+        # now we loop through the partitions...
+        for part in growable:
+            # calculate max number of sectors this request can grow
+            req_sectors = part.partedPartition.geometry.length
+            share = float(req_sectors) / float(disk_total)
+            max_grow = (share * disk_free)
+            if not limited[part.name]:
+                leftover_share = float(req_sectors) / float(unlimited_total)
+                max_grow += leftover_share * leftover
+            max_sectors = req_sectors + max_grow
+            max_mb = (max_sectors * sectorSize) / (1024 * 1024)
+
+            installer.log.debug("%s: base_size=%dMB, max_size=%sMB" %
+                    (part.name, part.req_base_size,  part.req_max_size))
+            installer.log.debug("%s: current_size=%dMB (%d sectors)" %
+                    (part.name, part.partedPartition.getSize(),
+                        part.partedPartition.geometry.length))
+            installer.log.debug("%s: %dMB (%d sectors, or %d%% of %d)" %
+                    (part.name, max_mb, max_sectors, share * 100, disk_free))
+
+            installer.log.debug("Checking constraints on max size...")
+            # don't grow beyond the request's maximum size
+            if part.req_max_size:
+                installer.log.debug("max_size: %dMB" % part.req_max_size)
+                # FIXME: round down to nearest cylinder boundary
+                req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
+                if req_max_sect < max_sectors:
+                    max_grow -= (max_sectors - req_max_sect)
+                    max_sectors = req_sectors + max_grow
+
+            # don't grow beyond the resident filesystem's max size
+            if part.format.maxSize > 0:
+                installer.log.debug("Format maxsize: %dMB" % part.format.maxSize)
+                # FIXME: round down to nearest cylinder boundary
+                fs_max_sect = (part.format.maxSize * (1024 * 1024)) / sectorSize
+                if fs_max_sect < max_sectors:
+                    max_grow -= (max_sectors - fs_max_sect)
+                    max_sectors = req_sectors + max_grow
+
+            # we can only grow as much as the largest free region on the disk
+            if free[0].length < max_grow:
+                installer.log.debug("Largest free region: %d sectors (%dMB)" %
+                        (free[0].length, free[0].getSize()))
+                # FIXME: round down to nearest cylinder boundary
+                max_grow = free[0].length
+                max_sectors = req_sectors + max_grow
+
+            # Now, we try to grow this partition as close to max_grow
+            # sectors as we can.
+            #
+            # We could call allocatePartitions after modifying this
+            # request and saving the original value of part.req_size,
+            # or we could try to use disk.maximizePartition().
+            max_size = (max_sectors * sectorSize) / (1024 * 1024)
+            orig_size = part.req_size
+            # try the max size to begin with
+            installer.log.debug("Attempting to allocate maximum size: %dMB" % max_size)
+            part.req_size = max_size
+            try:
+                allocatePartitions(installer, disks, partitions)
+            except PartitioningError, e:
+                installer.log.debug("Max size attempt failed: %s (%dMB)" % (part.name,
+                                                                            max_size))
+                part.req_size = orig_size
+            else:
+                continue
+
+            installer.log.debug("Starting binary search: size=%d max_size=%d" % (part.req_size, max_size))
+            count = 0
+            op_func = add
+            increment = max_grow
+            last_good_size = part.req_size
+            last_outcome = None
+            while count < 3:
+                last_size = part.req_size
+                increment /= 2
+                req_sectors = op_func(req_sectors, increment)
+                part.req_size = (req_sectors * sectorSize) / (1024 * 1024)
+                installer.log.debug("Attempting size=%dMB" % part.req_size)
+                count += 1
+                try:
+                    allocatePartitions(disks, partitions)
+                except PartitioningError, e:
+                    installer.log.debug("Attempt at %dMB failed" % part.req_size)
+                    op_func = sub
+                    last_outcome = False
+                else:
+                    op_func = add
+                    last_good_size = part.req_size
+                    last_outcome = True
+
+            if not last_outcome:
+                part.req_size = last_good_size
+                installer.log.debug("Backing up to size=%dMB" % part.req_size)
+                try:
+                    allocatePartitions(disks, partitions)
+                except PartitioningError, e:
+                    raise PartitioningError("Failed to grow partitions")
+
+    # reset all requests to their original requested size
+    for part in partitions:
+        if part.exists:
+            continue
+        part.req_size = part.req_base_size
+
+def growLVM(installer):
+    """ Grow LVs according to the sizes of the PVs. """
+    storage = installer.ds.storage
+    for vg in storage.vgs:
+        total_free = vg.freeSpace
+        if not total_free:
+            installer.log.debug("vg %s has no free space" % vg.name)
+            continue
+
+        installer.log.debug("vg %s: %dMB free ; lvs: %s" % (vg.name, vg.freeSpace,
+                                                  [l.lvname for l in vg.lvs]))
+
+        # figure out how much to grow each LV
+        grow_amounts = {}
+        lv_total = vg.size - total_free
+        installer.log.debug("used: %dMB ; vg.size: %dMB" % (lv_total, vg.size))
+
+        # This first loop is to calculate percentage-based growth
+        # amounts. These are based on total free space.
+        lvs = vg.lvs
+        lvs.sort(cmp=lvCompare)
+        for lv in lvs:
+            if not lv.req_grow or not lv.req_percent:
+                continue
+
+            portion = (lv.req_percent * 0.01)
+            grow = portion * vg.vgFree
+            new_size = lv.req_size + grow
+            if lv.req_max_size and new_size > lv.req_max_size:
+                grow -= (new_size - lv.req_max_size)
+
+            if lv.format.maxSize and lv.format.maxSize < new_size:
+                grow -= (new_size - lv.format.maxSize)
+
+            # clamp growth amount to a multiple of vg extent size
+            grow_amounts[lv.name] = vg.align(grow)
+            total_free -= grow
+            lv_total += grow
+
+        # This second loop is to calculate non-percentage-based growth
+        # amounts. These are based on free space remaining after
+        # calculating percentage-based growth amounts.
+
+        # keep a tab on space not allocated due to format or requested
+        # maximums -- we'll dole it out to subsequent requests
+        leftover = 0
+        for lv in lvs:
+            installer.log.debug("Checking lv %s: req_grow: %s ; req_percent: %s"
+                      % (lv.name, lv.req_grow, lv.req_percent))
+            if not lv.req_grow or lv.req_percent:
+                continue
+
+            portion = float(lv.req_size) / float(lv_total)
+            grow = portion * total_free
+            installer.log.debug("grow is %dMB" % grow)
+
+            todo = lvs[lvs.index(lv):]
+            unallocated = reduce(lambda x,y: x+y,
+                                 [l.req_size for l in todo
+                                  if l.req_grow and not l.req_percent])
+            extra_portion = float(lv.req_size) / float(unallocated)
+            extra = extra_portion * leftover
+            installer.log.debug("%s getting %dMB (%d%%) of %dMB leftover space"
+                      % (lv.name, extra, extra_portion * 100, leftover))
+            leftover -= extra
+            grow += extra
+            installer.log.debug("grow is now %dMB" % grow)
+            max_size = lv.req_size + grow
+            if lv.req_max_size and max_size > lv.req_max_size:
+                max_size = lv.req_max_size
+
+            if lv.format.maxSize and max_size > lv.format.maxSize:
+                max_size = lv.format.maxSize
+
+            installer.log.debug("max size is %dMB" % max_size)
+            max_size = max_size
+            leftover += (lv.req_size + grow) - max_size
+            grow = max_size - lv.req_size
+            installer.log.debug("lv %s gets %dMB" % (lv.name, vg.align(grow)))
+            grow_amounts[lv.name] = vg.align(grow)
+
+        if not grow_amounts:
+            installer.log.debug("No growable lvs in vg %s" % vg.name)
+            continue
+
+        # now grow the lvs by the amounts we've calculated above
+        for lv in lvs:
+            if lv.name not in grow_amounts.keys():
+                continue
+            lv.size += grow_amounts[lv.name]
+
+        # now there shouldn't be any free space left, but if there is we
+        # should allocate it to one of the LVs
+        vg_free = vg.freeSpace
+        installer.log.debug("vg %s has %dMB free" % (vg.name, vg_free))
+        if vg_free:
+            for lv in lvs:
+                if not lv.req_grow:
+                    continue
+
+                if lv.req_max_size and lv.size == lv.req_max_size:
+                    continue
+
+                if lv.format.maxSize and lv.size == lv.format.maxSize:
+                    continue
+
+                # first come, first served
+                projected = lv.size + vg.freeSpace
+                if lv.req_max_size and projected > lv.req_max_size:
+                    projected = lv.req_max_size
+
+                if lv.format.maxSize and projected > lv.format.maxSize:
+                    projected = lv.format.maxSize
+
+                installer.log.debug("Giving leftover %dMB to %s" % (projected - lv.size,
+                                                                    lv.name))
+                lv.size = projected
+
+def partitionCompare(part1, part2):
+    """ More specifically defined partitions come first.
+
+        < 1 => x < y
+          0 => x == y
+        > 1 => x > y
+    """
+    ret = 0
+
+    if part1.req_base_weight:
+        ret -= part1.req_base_weight
+
+    if part2.req_base_weight:
+        ret += part2.req_base_weight
+
+    # bootable partitions to the front
+    ret -= cmp(part1.req_bootable, part2.req_bootable) * 1000
+
+    # more specific disk specs to the front of the list
+    ret += cmp(len(part1.parents), len(part2.parents)) * 500
+
+    # primary-only to the front of the list
+    ret -= cmp(part1.req_primary, part2.req_primary) * 200
+
+    # larger requests go to the front of the list
+    ret -= cmp(part1.size, part2.size) * 100
+
+    # fixed size requests to the front
+    ret += cmp(part1.req_grow, part2.req_grow) * 50
+
+    # potentially larger growable requests go to the front
+    if part1.req_grow and part2.req_grow:
+        if not part1.req_max_size and part2.req_max_size:
+            ret -= 25
+        elif part1.req_max_size and not part2.req_max_size:
+            ret += 25
+        else:
+            ret -= cmp(part1.req_max_size, part2.req_max_size) * 25
+
+    if ret > 0:
+        ret = 1
+    elif ret < 0:
+        ret = -1
+
+    return ret
+
+def lvCompare(lv1, lv2):
+    """ More specifically defined lvs come first.
+
+        < 1 => x < y
+          0 => x == y
+        > 1 => x > y
+    """
+    ret = 0
+
+    # larger requests go to the front of the list
+    ret -= cmp(lv1.size, lv2.size) * 100
+
+    # fixed size requests to the front
+    ret += cmp(lv1.req_grow, lv2.req_grow) * 50
+
+    # potentially larger growable requests go to the front
+    if lv1.req_grow and lv2.req_grow:
+        if not lv1.req_max_size and lv2.req_max_size:
+            ret -= 25
+        elif lv1.req_max_size and not lv2.req_max_size:
+            ret += 25
+        else:
+            ret -= cmp(lv1.req_max_size, lv2.req_max_size) * 25
+
+    if ret > 0:
+        ret = 1
+    elif ret < 0:
+        ret = -1
+
+    return ret
+
+def getNextPartitionType(disk, no_primary=None):
+    """ Find the type of partition to create next on a disk.
+
+        Return a parted partition type value representing the type of the
+        next partition we will create on this disk.
+
+        If there is only one free primary partition and we can create an
+        extended partition, we do that.
+
+        If there are free primary slots and an extended partition we will
+        recommend creating a primary partition. This can be overridden
+        with the keyword argument no_primary.
+
+        Arguments:
+
+            disk -- a parted.Disk instance representing the disk
+
+        Keyword arguments:
+
+            no_primary -- given a choice between primary and logical
+                          partitions, prefer logical
+
+    """
+    part_type = None
+    extended = disk.getExtendedPartition()
+    supports_extended = disk.supportsFeature(parted.DISK_TYPE_EXTENDED)
+    logical_count = len(disk.getLogicalPartitions())
+    max_logicals = disk.getMaxLogicalPartitions()
+    primary_count = disk.primaryPartitionCount
+
+    if primary_count == disk.maxPrimaryPartitionCount and \
+       extended and logical_count < max_logicals:
+        part_type = parted.PARTITION_LOGICAL
+    elif primary_count == (disk.maxPrimaryPartitionCount - 1) and \
+         not extended and supports_extended:
+        # last chance to create an extended partition
+        part_type = parted.PARTITION_EXTENDED
+    elif no_primary and extended and logical_count < max_logicals:
+        # create a logical even though we could presumably create a
+        # primary instead
+        part_type = parted.PARTITION_LOGICAL
+    elif not no_primary:
+        # XXX there is a possiblity that the only remaining free space on
+        #     the disk lies within the extended partition, but we will
+        #     try to create a primary first
+        part_type = parted.PARTITION_NORMAL
+
+    return part_type
+
+def getBestFreeSpaceRegion(installer, disk, part_type, req_size,
+                           boot=None, best_free=None):
+    """ Return the "best" free region on the specified disk.
+
+        For non-boot partitions, we return the largest free region on the
+        disk. For boot partitions, we return the first region that is
+        large enough to hold the partition.
+
+        Partition type (parted's PARTITION_NORMAL, PARTITION_LOGICAL) is
+        taken into account when locating a suitable free region.
+
+        For locating the best region from among several disks, the keyword
+        argument best_free allows the specification of a current "best"
+        free region with which to compare the best from this disk. The
+        overall best region is returned.
+
+        Arguments:
+
+            disk -- the disk (a parted.Disk instance)
+            part_type -- the type of partition we want to allocate
+                         (one of parted's partition type constants)
+            req_size -- the requested size of the partition (in MB)
+
+        Keyword arguments:
+
+            boot -- indicates whether this will be a bootable partition
+                    (boolean)
+            best_free -- current best free region for this partition
+
+    """
+    #installer.log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB boot=%s best=%s" %
+    #                    (disk.device.path, part_type, req_size, boot, best_free))
+    extended = disk.getExtendedPartition()
+    for _range in disk.getFreeSpaceRegions():
+        if extended:
+            # find out if there is any overlap between this region and the
+            # extended partition
+            installer.log.debug("Looking for intersection between extended (%d-%d) and free (%d-%d)" %
+                         (extended.geometry.start, extended.geometry.end, _range.start, _range.end))
+
+            # parted.Geometry.overlapsWith can handle this
+            try:
+                free_geom = extended.geometry.intersect(_range)
+            except ArithmeticError, e:
+                # this freespace region does not lie within the extended
+                # partition's geometry
+                free_geom = None
+
+            if (free_geom and part_type == parted.PARTITION_NORMAL) or \
+               (not free_geom and part_type == parted.PARTITION_LOGICAL):
+                installer.log.debug("Free region not suitable for request")
+                continue
+
+            if part_type == parted.PARTITION_NORMAL:
+                # we're allocating a primary and the region is not within
+                # the extended, so we use the original region
+                free_geom = _range
+        else:
+            free_geom = _range
+
+        installer.log.debug("Current free range on %s is %d-%d (%dMB)" % (disk.device.path,
+                                                                          free_geom.start,
+                                                                          free_geom.end,
+                                                                          free_geom.getSize()))
+        free_size = free_geom.getSize()
+
+        if req_size <= free_size:
+            if not best_free or free_geom.length > best_free.length:
+                best_free = free_geom
+
+                if boot:
+                    # if this is a bootable partition we want to
+                    # use the first freespace region large enough
+                    # to satisfy the request
+                    break
+
+    return best_free
+
+def _scheduleLVs(installer, devs):
+    if installer.ds.storage.encryptedAutoPart:
+        pvs = []
+        for dev in devs:
+            pv = LUKSDevice("luks-%s" % dev.name,
+                            format=getFormat("lvmpv", device=dev.path),
+                            size=dev.size,
+                            parents=dev)
+            pvs.append(pv)
+            installer.ds.storage.createDevice(pv)
+    else:
+        pvs = devs
+
+    # create a vg containing all of the autopart pvs
+    vg = installer.ds.storage.newVG(pvs=pvs)
+    installer.ds.storage.createDevice(vg)
+
+    #
+    # Convert storage.autoPartitionRequests into Device instances and
+    # schedule them for creation.
+    #
+    # Second pass, for LVs only.
+    for request in installer.ds.storage.autoPartitionRequests:
+        if not request.asVol:
+            continue
+
+        if request.fstype is None:
+            request.fstype = installer.ds.storage.defaultFSType
+
+        # FIXME: move this to a function and handle exceptions
+        dev = installer.ds.storage.newLV(vg=vg,
+                                         fmt_type=request.fstype,
+                                         mountpoint=request.mountpoint,
+                                         grow=request.grow,
+                                         maxsize=request.maxSize,
+                                         size=request.size)
+
+        # schedule the device for creation
+        installer.ds.storage.createDevice(dev)
diff --git a/pkgs/core/pomona/src/storage_old/udev.py b/pkgs/core/pomona/src/storage_old/udev.py
new file mode 100644 (file)
index 0000000..a9a7377
--- /dev/null
@@ -0,0 +1,305 @@
+#!/usr/bin/python
+
+import os
+import sys
+
+import util
+
+def udev_settle(timeout=None):
+    argv = ["settle"]
+    if timeout:
+        argv.append("--timeout=%d" % int(timeout))
+
+    util.execWithRedirect("udevadm", argv, stderr="/dev/null", searchPath=1)
+
+def udev_trigger(subsystem=None):
+    argv = ["trigger"]
+    if subsystem:
+        argv.append("--subsystem-match=%s" % subsystem)
+
+    util.execWithRedirect("udevadm", argv, stderr="/dev/null", searchPath=1)
+
+def udev_get_block_devices():
+    #udev_settle(timeout=30)
+    entries = []
+    for path in enumerate_block_devices():
+        entry = udev_get_block_device(path)
+        if entry:
+            entries.append(entry)
+    return entries
+
+def __is_blacklisted_blockdev(dev_name):
+    """Is this a blockdev we never want for an install?"""
+    if dev_name.startswith("loop") or dev_name.startswith("ram") or dev_name.startswith("fd"):
+        return True
+
+    if os.path.exists("/sys/class/block/%s/device/model" %(dev_name,)):
+        model = open("/sys/class/block/%s/device/model" %(dev_name,)).read()
+        for bad in ("IBM *STMF KERNEL", "SCEI Flash-5", "DGC LUNZ"):
+            if model.find(bad) != -1:
+                return True
+
+    return False
+
+def enumerate_block_devices():
+    top_dir = "/sys/class/block"
+    devices = []
+    for dev_name in os.listdir(top_dir):
+        if __is_blacklisted_blockdev(dev_name):
+            continue
+        full_path = os.path.join(top_dir, dev_name)
+        link_ref = os.readlink(full_path)
+        real_path = os.path.join(top_dir, link_ref)
+        sysfs_path = os.path.normpath(real_path)
+        devices.append(sysfs_path)
+    return devices
+
+def udev_get_block_device(sysfs_path):
+    if not os.path.exists(sysfs_path):
+        return None
+
+    db_entry = sysfs_path[4:].replace("/", "\\x2f")
+    db_root = "/dev/.udev/db"
+    db_path = os.path.normpath("%s/%s" % (db_root, db_entry))
+    if not os.access(db_path, os.R_OK):
+        return None
+
+    entry = open(db_path).read()
+    dev = udev_parse_block_entry(entry)
+    if dev:
+        # XXX why do we do this? is /sys going to move during installation?
+        dev['sysfs_path'] = sysfs_path[4:]  # strip off the leading '/sys'
+        dev = udev_parse_uevent_file(dev)
+
+    # now add in the contents of the uevent file since they're handy
+    return dev
+
+def udev_parse_uevent_file(dev):
+    path = os.path.normpath("/sys/%s/uevent" % dev['sysfs_path'])
+    if not os.access(path, os.R_OK):
+        return dev
+
+    with open(path) as f:
+        for line in f.readlines():
+            (key, equals, value) = line.strip().partition("=")
+            if not equals:
+                continue
+            dev[key] = value
+
+    return dev
+
+def udev_parse_block_entry(buf):
+    dev = {'name': None,
+           'symlinks': []}
+
+    for line in buf.splitlines():
+        line.strip()
+        (tag, sep, val) = line.partition(":")
+        if not sep:
+            continue
+
+        if tag == "N":
+            dev['name'] = val
+        elif tag == "S":
+            dev['symlinks'].append(val)
+        elif tag == "E":
+            if val.count("=") > 1 and val.count(" ") > 0:
+                # eg: LVM2_LV_NAME when querying the VG for its LVs
+                vars = val.split()
+                vals = []
+                var_name = None
+                for (index, subval) in enumerate(vars):
+                    (var_name, sep, var_val) = subval.partition("=")
+                    if sep:
+                        vals.append(var_val)
+
+                dev[var_name] = vals
+            else:
+                (var_name, sep, var_val) = val.partition("=")
+                if not sep:
+                    continue
+
+                if var_val.count(" "):
+                    # eg: DEVLINKS
+                    var_val = var_val.split()
+
+                dev[var_name] = var_val
+
+    if dev.get("name"):
+        return dev
+
+# These are functions for retrieving specific pieces of information from
+# udev database entries.
+def udev_device_get_name(udev_info):
+    """ Return the best name for a device based on the udev db data. """
+    return udev_info.get("DM_NAME", udev_info["name"])
+
+def udev_device_get_format(udev_info):
+    """ Return a device's format type as reported by udev. """
+    return udev_info.get("ID_FS_TYPE")
+
+def udev_device_get_uuid(udev_info):
+    """ Get the UUID from the device's format as reported by udev. """
+    md_uuid = udev_info.get("MD_UUID")
+    uuid = udev_info.get("ID_FS_UUID")
+    # we don't want to return the array's uuid as a member's uuid
+    if uuid and not md_uuid == uuid:
+        return udev_info.get("ID_FS_UUID")
+
+def udev_device_get_label(udev_info):
+    """ Get the label from the device's format as reported by udev. """
+    return udev_info.get("ID_FS_LABEL")
+
+def udev_device_is_dm(info):
+    """ Return True if the device is a device-mapper device. """
+    return info.has_key("DM_NAME")
+
+def udev_device_is_md(info):
+    """ Return True is the device is an mdraid array device. """
+    return info.has_key("MD_METADATA")
+
+def udev_device_is_cdrom(info):
+    """ Return True if the device is an optical drive. """
+    # FIXME: how can we differentiate USB drives from CD-ROM drives?
+    #         -- USB drives also generate a sdX device.
+    return info.get("ID_CDROM") == "1"
+
+def udev_device_is_disk(info):
+    """ Return True is the device is a disk. """
+    has_range = os.path.exists("/sys/%s/range" % info['sysfs_path'])
+    return info.get("DEVTYPE") == "disk" or has_range
+
+def udev_device_is_partition(info):
+    has_start = os.path.exists("/sys/%s/start" % info['sysfs_path'])
+    return info.get("DEVTYPE") == "partition" or has_start
+
+def udev_device_get_sysfs_path(info):
+    return info['sysfs_path']
+
+def udev_device_get_major(info):
+    return int(info["MAJOR"])
+
+def udev_device_get_minor(info):
+    return int(info["MINOR"])
+
+def udev_device_get_md_level(info):
+    return info["MD_LEVEL"]
+
+def udev_device_get_md_devices(info):
+    return int(info["MD_DEVICES"])
+
+def udev_device_get_md_uuid(info):
+    return info["MD_UUID"]
+
+def udev_device_get_vg_name(info):
+    return info['LVM2_VG_NAME']
+
+def udev_device_get_vg_uuid(info):
+    return info['LVM2_VG_UUID']
+
+def udev_device_get_vg_size(info):
+    # lvm's decmial precision is not configurable, so we tell it to use
+    # KB and convert to MB here
+    return float(info['LVM2_VG_SIZE']) / 1024
+
+def udev_device_get_vg_free(info):
+    # lvm's decmial precision is not configurable, so we tell it to use
+    # KB and convert to MB here
+    return float(info['LVM2_VG_FREE']) / 1024
+
+def udev_device_get_vg_extent_size(info):
+    # lvm's decmial precision is not configurable, so we tell it to use
+    # KB and convert to MB here
+    return float(info['LVM2_VG_EXTENT_SIZE']) / 1024
+
+def udev_device_get_vg_extent_count(info):
+    return int(info['LVM2_VG_EXTENT_COUNT'])
+
+def udev_device_get_vg_free_extents(info):
+    return int(info['LVM2_VG_FREE_COUNT'])
+
+def udev_device_get_vg_pv_count(info):
+    return int(info['LVM2_PV_COUNT'])
+
+def udev_device_get_pv_pe_start(info):
+    # lvm's decmial precision is not configurable, so we tell it to use
+    # KB and convert to MB here
+    return float(info['LVM2_PE_START']) / 1024
+
+def udev_device_get_lv_names(info):
+    names = info['LVM2_LV_NAME']
+    if not names:
+        names = []
+    elif not isinstance(names, list):
+        names = [names]
+    return names
+
+def udev_device_get_lv_uuids(info):
+    uuids = info['LVM2_LV_UUID']
+    if not uuids:
+        uuids = []
+    elif not isinstance(uuids, list):
+        uuids = [uuids]
+    return uuids
+
+def udev_device_get_lv_sizes(info):
+    # lvm's decmial precision is not configurable, so we tell it to use
+    # KB and convert to MB here
+    sizes = info['LVM2_LV_SIZE']
+    if not sizes:
+        sizes = []
+    elif not isinstance(sizes, list):
+        sizes = [sizes]
+
+    return [float(s) / 1024 for s in sizes]
+
+def udev_device_is_dmraid(info):
+    # Note that this function does *not* identify raid sets.
+    # Tests to see if device is parto of a dmraid set.
+    # dmraid and mdriad have the same ID_FS_USAGE string,  ID_FS_TYPE has a
+    # string that describes the type of dmraid (isw_raid_member...),  I don't
+    # want to maintain a list and mdraid's ID_FS_TYPE='linux_raid_member', so
+    # dmraid will be everything that is raid and not linux_raid_member
+    #from formats.dmraid import DMRaidMember
+    #if info.has_key("ID_FS_TYPE") and \
+    #        info["ID_FS_TYPE"] in DMRaidMember._udevTypes:
+    #    return True
+    #
+    return False
+
+def udev_device_get_dmraid_partition_disk(info):
+    try:
+        p_index = info["DM_NAME"].rindex("p")
+    except (KeyError, AttributeError, ValueError):
+        return None
+
+    if not info["DM_NAME"][p_index+1:].isdigit():
+        return None
+
+    return info["DM_NAME"][:p_index]
+
+def udev_device_is_dmraid_partition(info, devicetree):
+    #diskname = udev_device_get_dmraid_partition_disk(info)
+    #dmraid_devices = devicetree.getDevicesByType("dm-raid array")
+    #
+    #for device in dmraid_devices:
+    #    if diskname == device.name:
+    #        return True
+    #
+    return False
+
+if __name__ == "__main__":
+    for device in udev_get_block_devices():
+        print udev_device_get_name(device)
+        print " Label   :", udev_device_get_label(device)
+        print " UUID    :", udev_device_get_uuid(device)
+        print " Format  :", udev_device_get_format(device)
+        print " Is disk :", udev_device_is_disk(device)
+        print " Is cdrom:", udev_device_is_cdrom(device)
+        print " Is part :", udev_device_is_partition(device)
+        print " Is dm   :", udev_device_is_dm(device)
+        print " Is md   :", udev_device_is_md(device)
+        #syspath = "/sys" + udev_device_get_sysfs_path(device)
+        #for (key, value) in udev_get_block_device(syspath).items():
+        #    print " (%s : %s)" % (key, value,)
+        print
diff --git a/pkgs/core/pomona/src/storage_test.py b/pkgs/core/pomona/src/storage_test.py
new file mode 100755 (executable)
index 0000000..c6a0b23
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/python
+
+import sys
+
+import storage