From: Michael Tremer Date: Wed, 10 Mar 2010 16:32:31 +0000 (+0100) Subject: pomona: New package. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc1e0b3f9ff3ba6bb7f42d05baec195e826562f7;p=ipfire-3.x.git pomona: New package. --- diff --git a/pkgs/core/pomona/pomona.nm b/pkgs/core/pomona/pomona.nm new file mode 100644 index 000000000..b5635b9df --- /dev/null +++ b/pkgs/core/pomona/pomona.nm @@ -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 . # +# # +############################################################################### + +############################################################################### +# 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 diff --git a/src/pomona/Makefile b/pkgs/core/pomona/src/Makefile similarity index 100% rename from src/pomona/Makefile rename to pkgs/core/pomona/src/Makefile diff --git a/src/pomona/Makefile.inc b/pkgs/core/pomona/src/Makefile.inc similarity index 98% rename from src/pomona/Makefile.inc rename to pkgs/core/pomona/src/Makefile.inc index 86830fef2..998fae836 100644 --- a/src/pomona/Makefile.inc +++ b/pkgs/core/pomona/src/Makefile.inc @@ -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 diff --git a/src/pomona/autopart.py b/pkgs/core/pomona/src/autopart.py similarity index 100% rename from src/pomona/autopart.py rename to pkgs/core/pomona/src/autopart.py diff --git a/src/pomona/backend.py b/pkgs/core/pomona/src/backend.py similarity index 100% rename from src/pomona/backend.py rename to pkgs/core/pomona/src/backend.py diff --git a/src/pomona/bootloader.py b/pkgs/core/pomona/src/bootloader.py similarity index 100% rename from src/pomona/bootloader.py rename to pkgs/core/pomona/src/bootloader.py diff --git a/src/pomona/console.py b/pkgs/core/pomona/src/console.py similarity index 100% rename from src/pomona/console.py rename to pkgs/core/pomona/src/console.py diff --git a/src/pomona/constants.py b/pkgs/core/pomona/src/constants.py similarity index 100% rename from src/pomona/constants.py rename to pkgs/core/pomona/src/constants.py diff --git a/src/pomona/cryptodev.py b/pkgs/core/pomona/src/cryptodev.py similarity index 100% rename from src/pomona/cryptodev.py rename to pkgs/core/pomona/src/cryptodev.py diff --git a/src/pomona/dispatch.py b/pkgs/core/pomona/src/dispatch.py similarity index 100% rename from src/pomona/dispatch.py rename to pkgs/core/pomona/src/dispatch.py diff --git a/src/pomona/dmraid.py b/pkgs/core/pomona/src/dmraid.py similarity index 100% rename from src/pomona/dmraid.py rename to pkgs/core/pomona/src/dmraid.py diff --git a/src/pomona/errors.py b/pkgs/core/pomona/src/errors.py similarity index 100% rename from src/pomona/errors.py rename to pkgs/core/pomona/src/errors.py diff --git a/src/pomona/exception.py b/pkgs/core/pomona/src/exception.py similarity index 100% rename from src/pomona/exception.py rename to pkgs/core/pomona/src/exception.py diff --git a/src/pomona/flags.py b/pkgs/core/pomona/src/flags.py similarity index 100% rename from src/pomona/flags.py rename to pkgs/core/pomona/src/flags.py diff --git a/src/pomona/fsset.py b/pkgs/core/pomona/src/fsset.py similarity index 100% rename from src/pomona/fsset.py rename to pkgs/core/pomona/src/fsset.py diff --git a/src/pomona/installer.py b/pkgs/core/pomona/src/installer.py similarity index 100% rename from src/pomona/installer.py rename to pkgs/core/pomona/src/installer.py diff --git a/src/pomona/instdata.py b/pkgs/core/pomona/src/instdata.py similarity index 100% rename from src/pomona/instdata.py rename to pkgs/core/pomona/src/instdata.py diff --git a/src/pomona/isys/Makefile b/pkgs/core/pomona/src/isys/Makefile similarity index 98% rename from src/pomona/isys/Makefile rename to pkgs/core/pomona/src/isys/Makefile index ba0fc5bc1..e462c0f99 100644 --- a/src/pomona/isys/Makefile +++ b/pkgs/core/pomona/src/isys/Makefile @@ -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/src/pomona/isys/devices.c b/pkgs/core/pomona/src/isys/devices.c similarity index 100% rename from src/pomona/isys/devices.c rename to pkgs/core/pomona/src/isys/devices.c diff --git a/src/pomona/isys/devices.h b/pkgs/core/pomona/src/isys/devices.h similarity index 100% rename from src/pomona/isys/devices.h rename to pkgs/core/pomona/src/isys/devices.h diff --git a/src/pomona/isys/eddsupport.c b/pkgs/core/pomona/src/isys/eddsupport.c similarity index 100% rename from src/pomona/isys/eddsupport.c rename to pkgs/core/pomona/src/isys/eddsupport.c diff --git a/src/pomona/isys/eddsupport.h b/pkgs/core/pomona/src/isys/eddsupport.h similarity index 100% rename from src/pomona/isys/eddsupport.h rename to pkgs/core/pomona/src/isys/eddsupport.h diff --git a/src/pomona/isys/ethtool.c b/pkgs/core/pomona/src/isys/ethtool.c similarity index 100% rename from src/pomona/isys/ethtool.c rename to pkgs/core/pomona/src/isys/ethtool.c diff --git a/src/pomona/isys/imount.c b/pkgs/core/pomona/src/isys/imount.c similarity index 100% rename from src/pomona/isys/imount.c rename to pkgs/core/pomona/src/isys/imount.c diff --git a/src/pomona/isys/imount.h b/pkgs/core/pomona/src/isys/imount.h similarity index 100% rename from src/pomona/isys/imount.h rename to pkgs/core/pomona/src/isys/imount.h diff --git a/src/pomona/isys/isofs.c b/pkgs/core/pomona/src/isys/isofs.c similarity index 100% rename from src/pomona/isys/isofs.c rename to pkgs/core/pomona/src/isys/isofs.c diff --git a/src/pomona/isys/isys.c b/pkgs/core/pomona/src/isys/isys.c similarity index 100% rename from src/pomona/isys/isys.c rename to pkgs/core/pomona/src/isys/isys.c diff --git a/src/pomona/isys/isys.h b/pkgs/core/pomona/src/isys/isys.h similarity index 100% rename from src/pomona/isys/isys.h rename to pkgs/core/pomona/src/isys/isys.h diff --git a/src/pomona/isys/isys.py b/pkgs/core/pomona/src/isys/isys.py similarity index 100% rename from src/pomona/isys/isys.py rename to pkgs/core/pomona/src/isys/isys.py diff --git a/src/pomona/isys/lang.c b/pkgs/core/pomona/src/isys/lang.c similarity index 100% rename from src/pomona/isys/lang.c rename to pkgs/core/pomona/src/isys/lang.c diff --git a/src/pomona/isys/lang.h b/pkgs/core/pomona/src/isys/lang.h similarity index 100% rename from src/pomona/isys/lang.h rename to pkgs/core/pomona/src/isys/lang.h diff --git a/src/pomona/isys/linkdetect.c b/pkgs/core/pomona/src/isys/linkdetect.c similarity index 100% rename from src/pomona/isys/linkdetect.c rename to pkgs/core/pomona/src/isys/linkdetect.c diff --git a/src/pomona/isys/net.h b/pkgs/core/pomona/src/isys/net.h similarity index 100% rename from src/pomona/isys/net.h rename to pkgs/core/pomona/src/isys/net.h diff --git a/src/pomona/isys/smp.c b/pkgs/core/pomona/src/isys/smp.c similarity index 100% rename from src/pomona/isys/smp.c rename to pkgs/core/pomona/src/isys/smp.c diff --git a/src/pomona/isys/smp.h b/pkgs/core/pomona/src/isys/smp.h similarity index 100% rename from src/pomona/isys/smp.h rename to pkgs/core/pomona/src/isys/smp.h diff --git a/src/pomona/isys/str.c b/pkgs/core/pomona/src/isys/str.c similarity index 100% rename from src/pomona/isys/str.c rename to pkgs/core/pomona/src/isys/str.c diff --git a/src/pomona/isys/str.h b/pkgs/core/pomona/src/isys/str.h similarity index 100% rename from src/pomona/isys/str.h rename to pkgs/core/pomona/src/isys/str.h diff --git a/src/pomona/isys/stubs.h b/pkgs/core/pomona/src/isys/stubs.h similarity index 100% rename from src/pomona/isys/stubs.h rename to pkgs/core/pomona/src/isys/stubs.h diff --git a/src/pomona/isys/sundries.h b/pkgs/core/pomona/src/isys/sundries.h similarity index 100% rename from src/pomona/isys/sundries.h rename to pkgs/core/pomona/src/isys/sundries.h diff --git a/src/pomona/isys/vio.c b/pkgs/core/pomona/src/isys/vio.c similarity index 100% rename from src/pomona/isys/vio.c rename to pkgs/core/pomona/src/isys/vio.c diff --git a/src/pomona/isys/wireless.c b/pkgs/core/pomona/src/isys/wireless.c similarity index 100% rename from src/pomona/isys/wireless.c rename to pkgs/core/pomona/src/isys/wireless.c diff --git a/src/pomona/isys/wireless.h b/pkgs/core/pomona/src/isys/wireless.h similarity index 100% rename from src/pomona/isys/wireless.h rename to pkgs/core/pomona/src/isys/wireless.h diff --git a/src/pomona/iutil.py b/pkgs/core/pomona/src/iutil.py similarity index 100% rename from src/pomona/iutil.py rename to pkgs/core/pomona/src/iutil.py diff --git a/src/pomona/keyboard_models.py b/pkgs/core/pomona/src/keyboard_models.py similarity index 100% rename from src/pomona/keyboard_models.py rename to pkgs/core/pomona/src/keyboard_models.py diff --git a/src/pomona/lang-table b/pkgs/core/pomona/src/lang-table similarity index 100% rename from src/pomona/lang-table rename to pkgs/core/pomona/src/lang-table diff --git a/src/pomona/lvm.py b/pkgs/core/pomona/src/lvm.py similarity index 100% rename from src/pomona/lvm.py rename to pkgs/core/pomona/src/lvm.py diff --git a/src/pomona/network.py b/pkgs/core/pomona/src/network.py similarity index 100% rename from src/pomona/network.py rename to pkgs/core/pomona/src/network.py diff --git a/src/pomona/packages.py b/pkgs/core/pomona/src/packages.py similarity index 100% rename from src/pomona/packages.py rename to pkgs/core/pomona/src/packages.py diff --git a/src/pomona/pakfireinstall.py b/pkgs/core/pomona/src/pakfireinstall.py similarity index 100% rename from src/pomona/pakfireinstall.py rename to pkgs/core/pomona/src/pakfireinstall.py diff --git a/src/pomona/partErrors.py b/pkgs/core/pomona/src/partErrors.py similarity index 100% rename from src/pomona/partErrors.py rename to pkgs/core/pomona/src/partErrors.py diff --git a/src/pomona/partIntfHelpers.py b/pkgs/core/pomona/src/partIntfHelpers.py similarity index 100% rename from src/pomona/partIntfHelpers.py rename to pkgs/core/pomona/src/partIntfHelpers.py diff --git a/src/pomona/partRequests.py b/pkgs/core/pomona/src/partRequests.py similarity index 100% rename from src/pomona/partRequests.py rename to pkgs/core/pomona/src/partRequests.py diff --git a/src/pomona/partedUtils.py b/pkgs/core/pomona/src/partedUtils.py similarity index 100% rename from src/pomona/partedUtils.py rename to pkgs/core/pomona/src/partedUtils.py diff --git a/src/pomona/partitioning.py b/pkgs/core/pomona/src/partitioning.py similarity index 100% rename from src/pomona/partitioning.py rename to pkgs/core/pomona/src/partitioning.py diff --git a/src/pomona/partitions.py b/pkgs/core/pomona/src/partitions.py similarity index 100% rename from src/pomona/partitions.py rename to pkgs/core/pomona/src/partitions.py diff --git a/src/pomona/po/Makefile b/pkgs/core/pomona/src/po/Makefile similarity index 100% rename from src/pomona/po/Makefile rename to pkgs/core/pomona/src/po/Makefile diff --git a/src/pomona/po/da.po b/pkgs/core/pomona/src/po/da.po similarity index 100% rename from src/pomona/po/da.po rename to pkgs/core/pomona/src/po/da.po diff --git a/src/pomona/po/de.po b/pkgs/core/pomona/src/po/de.po similarity index 100% rename from src/pomona/po/de.po rename to pkgs/core/pomona/src/po/de.po diff --git a/src/pomona/po/pomona.pot b/pkgs/core/pomona/src/po/pomona.pot similarity index 100% rename from src/pomona/po/pomona.pot rename to pkgs/core/pomona/src/po/pomona.pot diff --git a/src/pomona/pomona b/pkgs/core/pomona/src/pomona similarity index 100% rename from src/pomona/pomona rename to pkgs/core/pomona/src/pomona diff --git a/src/pomona/pomona_log.py b/pkgs/core/pomona/src/pomona_log.py similarity index 100% rename from src/pomona/pomona_log.py rename to pkgs/core/pomona/src/pomona_log.py diff --git a/src/pomona/pychecker-false-positives b/pkgs/core/pomona/src/pychecker-false-positives similarity index 100% rename from src/pomona/pychecker-false-positives rename to pkgs/core/pomona/src/pychecker-false-positives diff --git a/src/pomona/raid.py b/pkgs/core/pomona/src/raid.py similarity index 100% rename from src/pomona/raid.py rename to pkgs/core/pomona/src/raid.py diff --git a/src/pomona/runpychecker.sh b/pkgs/core/pomona/src/runpychecker.sh similarity index 100% rename from src/pomona/runpychecker.sh rename to pkgs/core/pomona/src/runpychecker.sh diff --git a/src/pomona/scripts/getlangnames.py b/pkgs/core/pomona/src/scripts/getlangnames.py similarity index 100% rename from src/pomona/scripts/getlangnames.py rename to pkgs/core/pomona/src/scripts/getlangnames.py diff --git a/pkgs/core/pomona/src/storage_old/__init__.py b/pkgs/core/pomona/src/storage_old/__init__.py new file mode 100644 index 000000000..a6cc2e7e8 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/__init__.py @@ -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 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 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 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("\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 index 000000000..9361b5229 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/deviceaction.py @@ -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 index 000000000..e69de29bb 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 index 000000000..52c9eddcf --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/devicelibs/crypto.py @@ -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 . +# +# Author(s): Dave Lehman +# Martin Sivak +# + +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 index 000000000..20fa02ac2 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/devicelibs/lvm.py @@ -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 index 000000000..57a40656e --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/devicelibs/swap.py @@ -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 index 000000000..e1ba7de07 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/devices.py @@ -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 index 000000000..042941c66 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/devicetree.py @@ -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 index 000000000..7a02dc084 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/errors.py @@ -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 index 000000000..f73f13840 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/formats/__init__.py @@ -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 index 000000000..2eace6153 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/formats/fs.py @@ -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 :") + + @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 index 000000000..25d9db4f1 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/formats/luks.py @@ -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 index 000000000..cccff87db --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/formats/lvmpv.py @@ -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 index 000000000..487d3656b --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/formats/swap.py @@ -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 index 000000000..d7672a689 --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/partitioning.py @@ -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 index 000000000..a9a73774e --- /dev/null +++ b/pkgs/core/pomona/src/storage_old/udev.py @@ -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 index 000000000..c6a0b2365 --- /dev/null +++ b/pkgs/core/pomona/src/storage_test.py @@ -0,0 +1,5 @@ +#!/usr/bin/python + +import sys + +import storage diff --git a/src/pomona/timezone.py b/pkgs/core/pomona/src/timezone.py similarity index 100% rename from src/pomona/timezone.py rename to pkgs/core/pomona/src/timezone.py diff --git a/src/pomona/tui.py b/pkgs/core/pomona/src/tui.py similarity index 100% rename from src/pomona/tui.py rename to pkgs/core/pomona/src/tui.py diff --git a/src/pomona/tui_bootloader.py b/pkgs/core/pomona/src/tui_bootloader.py similarity index 100% rename from src/pomona/tui_bootloader.py rename to pkgs/core/pomona/src/tui_bootloader.py diff --git a/src/pomona/tui_complete.py b/pkgs/core/pomona/src/tui_complete.py similarity index 100% rename from src/pomona/tui_complete.py rename to pkgs/core/pomona/src/tui_complete.py diff --git a/src/pomona/tui_confirm.py b/pkgs/core/pomona/src/tui_confirm.py similarity index 100% rename from src/pomona/tui_confirm.py rename to pkgs/core/pomona/src/tui_confirm.py diff --git a/src/pomona/tui_keyboard.py b/pkgs/core/pomona/src/tui_keyboard.py similarity index 100% rename from src/pomona/tui_keyboard.py rename to pkgs/core/pomona/src/tui_keyboard.py diff --git a/src/pomona/tui_language.py b/pkgs/core/pomona/src/tui_language.py similarity index 100% rename from src/pomona/tui_language.py rename to pkgs/core/pomona/src/tui_language.py diff --git a/src/pomona/tui_network.py b/pkgs/core/pomona/src/tui_network.py similarity index 100% rename from src/pomona/tui_network.py rename to pkgs/core/pomona/src/tui_network.py diff --git a/src/pomona/tui_partition.py b/pkgs/core/pomona/src/tui_partition.py similarity index 100% rename from src/pomona/tui_partition.py rename to pkgs/core/pomona/src/tui_partition.py diff --git a/src/pomona/tui_progress.py b/pkgs/core/pomona/src/tui_progress.py similarity index 100% rename from src/pomona/tui_progress.py rename to pkgs/core/pomona/src/tui_progress.py diff --git a/src/pomona/tui_timezone.py b/pkgs/core/pomona/src/tui_timezone.py similarity index 100% rename from src/pomona/tui_timezone.py rename to pkgs/core/pomona/src/tui_timezone.py diff --git a/src/pomona/tui_userauth.py b/pkgs/core/pomona/src/tui_userauth.py similarity index 100% rename from src/pomona/tui_userauth.py rename to pkgs/core/pomona/src/tui_userauth.py diff --git a/src/pomona/tui_welcome.py b/pkgs/core/pomona/src/tui_welcome.py similarity index 100% rename from src/pomona/tui_welcome.py rename to pkgs/core/pomona/src/tui_welcome.py diff --git a/src/pomona/users.py b/pkgs/core/pomona/src/users.py similarity index 100% rename from src/pomona/users.py rename to pkgs/core/pomona/src/users.py diff --git a/src/pomona/zonetab.py b/pkgs/core/pomona/src/zonetab.py similarity index 100% rename from src/pomona/zonetab.py rename to pkgs/core/pomona/src/zonetab.py