--- /dev/null
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2007, 2008 Michael Tremer & Christian Schmidt #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+###############################################################################
+# Definitions
+###############################################################################
+
+include $(PKGROOT)/Include
+
+PKG_NAME = pomona
+PKG_VER =
+PKG_REL = 0
+
+PKG_MAINTAINER =
+PKG_GROUP = System/Installer
+PKG_URL = http://www.ipfire.org
+PKG_LICENSE = GPLv3+
+PKG_SUMMARY = The IPFire 3.x installer.
+
+PKG_BUILD_DEPS+= pychecker
+PKG_DEPS += e2fsprogs parted pciutils popt \
+ pyfire python python-dbus python-parted
+
+define PKG_DESCRIPTION
+ Pomona is the installer for IPFire 3.x.
+endef
+
+DIR_APP = src
+
+define STAGE_PREPARE
+ cd $(DIR_APP) && make clean
+ #cd $(DIR_APP) && make -C po update-po
+endef
+
+define STAGE_BUILD
+ cd $(DIR_APP) && make
+endef
+
+define STAGE_TEST
+ cd $(DIR_APP) && make test
+endef
+
+define STAGE_INSTALL
+ cd $(DIR_APP) && make install DESTDIR=$(BUILDROOT) \
+ NAME=$(NAME) SNAME=$(SNAME) VERSION=$(VERSION) KVER=$(KVER)
+ cd $(DIR_APP) && make clean
+endef
DESTDIR=$(INSTALLER_DIR)
-INSTALL = /usr/bin/install -c
+INSTALL = install -c
INSTALL_PROGRAM = ${INSTALL}
INSTALL_DATA = ${INSTALL} -m 644
INSTALLNLSDIR = $(DESTDIR)/usr/share/locale
rm -f depend
install: all
+ -mkdir -p $(PYTHONLIBDIR)
install -s $(PYMODULES) $(PYTHONLIBDIR)
install -m 644 isys.py $(PYTHONLIBDIR)
--- /dev/null
+#!/usr/bin/python
+
+from devicetree import DeviceTree
+
+from deviceaction import *
+from devicelibs import lvm
+from devicelibs.lvm import safeLvmName
+from devices import *
+from formats import get_default_filesystem_type
+from udev import *
+
+from constants import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+def storageInitialize(installer):
+ storage = installer.ds.storage
+
+ storage.shutdown()
+
+ if installer.dispatch.dir == DISPATCH_BACK:
+ return
+
+ # XXX I don't understand why I have to do this
+ udev_trigger(subsystem="block")
+
+ #XXX Determine our cdrom drive/usb key here and add it to protectedPartiotions
+ storage.reset()
+
+class Storage(object):
+ def __init__(self, installer):
+ self.installer = installer
+
+ self.protectedDisks = []
+ self.clearDisks = []
+ self.ignoredDisks = []
+
+ self.defaultFSType = get_default_filesystem_type()
+ self.defaultBootFSType = get_default_filesystem_type(boot=True)
+
+ self.doAutoPartition = False
+ self.encryptedAutoPart = False
+ #self.autoPartitionRequests = []
+ self.autoPartitionRequests = [PartSpec(mountpoint="/", fstype=self.defaultFSType, size=1024, grow=True),
+ PartSpec(mountpoint="/boot", fstype=self.defaultFSType, size=75, grow=False),]
+
+ #self.devicetree = DeviceTree(self.installer)
+ self.devicetree = None
+
+ self._nextID = 0
+
+ def shutdown(self):
+ self.installer.log.debug("Shutting down storage...")
+
+ def reset(self):
+ """ Reset storage configuration to reflect actual system state.
+
+ This should rescan from scratch but not clobber user-obtained
+ information like passphrases
+ """
+ #for device in self.devices:
+ # if device.format.type == "luks" and device.format.exists:
+ # self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase
+
+ self.installer.window = self.installer.intf.waitWindow(_("Finding Devices"),
+ _("Finding storage devices..."))
+ self.devicetree = DeviceTree(self.installer)
+ self.devicetree.populate()
+ self.fsset = FSSet(self.installer)
+ self.installer.window.pop()
+
+ def checkNoDisks(self):
+ """Check that there are valid disk devices."""
+ if not self.disks:
+ self.installer.intf.messageWindow(_("No Drives Found"),
+ _("An error has occurred - no valid devices were "
+ "found on which to create new file systems. "
+ "Please check your hardware for the cause "
+ "of this problem."))
+ return True
+ return False
+
+ def sanityCheck(self):
+ """ Run a series of tests to verify the storage configuration.
+
+ This function is called at the end of partitioning so that
+ we can make sure you don't have anything silly (like no /,
+ a really small /, etc). Returns (errors, warnings) where
+ each is a list of strings.
+ """
+ checkSizes = [('/usr', 250), ('/tmp', 50), ('/var', 384),
+ ('/home', 100), ('/boot', 75)]
+ warnings = []
+ errors = []
+
+ filesystems = self.fsset.mountpoints
+ root = self.fsset.rootDevice
+ swaps = self.fsset.swapDevices
+ #try:
+ # boot = self.anaconda.platform.bootDevice()
+ #except DeviceError:
+ # boot = None
+ boot = None
+
+ if not root:
+ errors.append(_("You have not defined a root partition (/), "
+ "which is required for installation of %s "
+ "to continue.") % (PRODUCT_NAME,))
+
+ if root and root.size < 250:
+ warnings.append(_("Your root partition is less than 250 "
+ "megabytes which is usually too small to "
+ "install %s.") % (PRODUCT_NAME,))
+
+ recommended_size = 1024
+ if (root and root.size < recommended_size):
+ errors.append(_("Your / partition is less than %s "
+ "megabytes which is lower than recommended "
+ "for a normal %s install.")
+ %(recommended_size, PRODUCT_NAME))
+
+ # livecds have to have the rootfs type match up
+ #if (root and
+ # self.installer.backend.rootFsType and
+ # root.format.type != self.installer.backend.rootFsType):
+ # errors.append(_("Your / partition does not match the "
+ # "the live image you are installing from. "
+ # "It must be formatted as %s.")
+ # % (self.anaconda.backend.rootFsType,))
+
+ for (mount, size) in checkSizes:
+ if mount in filesystems and filesystems[mount].size < size:
+ warnings.append(_("Your %s partition is less than %s "
+ "megabytes which is lower than recommended "
+ "for a normal %s install.")
+ %(mount, size, PRODUCT_NAME))
+
+ usb_disks = []
+ firewire_disks = []
+ #for disk in self.disks:
+ # if isys.driveUsesModule(disk.name, ["usb-storage", "ub"]):
+ # usb_disks.append(disk)
+ # elif isys.driveUsesModule(disk.name, ["sbp2", "firewire-sbp2"]):
+ # firewire_disks.append(disk)
+
+ uses_usb = False
+ uses_firewire = False
+ for device in filesystems.values():
+ for disk in usb_disks:
+ if device.dependsOn(disk):
+ uses_usb = True
+ break
+
+ for disk in firewire_disks:
+ if device.dependsOn(disk):
+ uses_firewire = True
+ break
+
+ if uses_usb:
+ warnings.append(_("Installing on a USB device. This may "
+ "or may not produce a working system."))
+ if uses_firewire:
+ warnings.append(_("Installing on a FireWire device. This may "
+ "or may not produce a working system."))
+
+ if not boot:
+ errors.append(_("You have not created a boot partition."))
+
+ if (boot and boot.type == "mdarray" and
+ boot.level != 1):
+ errors.append(_("Bootable partitions can only be on RAID1 "
+ "devices."))
+
+ # can't have bootable partition on LV
+ if boot and boot.type == "lvmlv":
+ errors.append(_("Bootable partitions cannot be on a "
+ "logical volume."))
+
+ # most arches can't have boot on RAID
+ if boot and boot.type == "mdarray" and not self.anaconda.platform.supportsMdRaidBoot:
+ errors.append(_("Bootable partitions cannot be on a RAID "
+ "device."))
+
+ # Lots of filesystems types don't support /boot.
+ if boot and not boot.format.bootable:
+ errors.append(_("Bootable partitions cannot be on an %s "
+ "filesystem.") % boot.format.name)
+
+ # vfat /boot is insane.
+ if (boot and boot == root and boot.format.type == "vfat"):
+ errors.append(_("Bootable partitions cannot be on an %s "
+ "filesystem.") % boot.format.type)
+
+ if (boot and filter(lambda d: d.type == "luks/dm-crypt",
+ self.deviceDeps(boot))):
+ errors.append(_("Bootable partitions cannot be on an "
+ "encrypted block device"))
+
+ if not swaps:
+ warnings.append(_("You have not specified a swap partition. "
+ "Although not strictly required in all cases, "
+ "it will significantly improve performance for "
+ "most installations."))
+
+ return (errors, warnings)
+
+ def deviceDeps(self, device):
+ return self.devicetree.getDependentDevices(device)
+
+ @property
+ def nextID(self):
+ id = self._nextID
+ self._nextID += 1
+ return id
+
+ @property
+ def disks(self):
+ """ A list of the disks in the device tree.
+
+ Ignored disks are not included, as are disks with no media present.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ disks = []
+ devices = self.devicetree.devices
+ for d in devices:
+ if isinstance(devices[d], DiskDevice) and devices[d].mediaPresent:
+ disks.append(devices[d])
+ disks.sort(key=lambda d: d.name)
+ return disks
+
+ @property
+ def devices(self):
+ """ A list of all the devices in the device tree. """
+ devices = self.devicetree.devices.values()
+ devices.sort(key=lambda d: d.path)
+ return devices
+
+ @property
+ def partitions(self):
+ """ A list of the partitions in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ partitions = self.devicetree.getDevicesByInstance(PartitionDevice)
+ partitions.sort(key=lambda d: d.name)
+ return partitions
+
+ @property
+ def vgs(self):
+ """ A list of the LVM Volume Groups in the device tree.
+
+ This is based on the current state of the device tree and
+ does not necessarily reflect the actual on-disk state of the
+ system's disks.
+ """
+ vgs = self.devicetree.getDevicesByType("lvmvg")
+ vgs.sort(key=lambda d: d.name)
+ return vgs
+
+ def createDevice(self, device):
+ """ Schedule creation of a device.
+
+ TODO: We could do some things here like assign the next
+ available raid minor if one isn't already set.
+ """
+ self.devicetree.registerAction(ActionCreateDevice(self.installer, device))
+ if device.format.type:
+ self.devicetree.registerAction(ActionCreateFormat(self.installer, device))
+
+ def destroyDevice(self, device):
+ """ Schedule destruction of a device. """
+ if device.format.exists and device.format.type:
+ # schedule destruction of any formatting while we're at it
+ self.devicetree.registerAction(ActionDestroyFormat(self.installer, device))
+
+ action = ActionDestroyDevice(self.installer, device)
+ self.devicetree.registerAction(action)
+
+ def newPartition(self, *args, **kwargs):
+ """ Return a new PartitionDevice instance for configuring. """
+ if kwargs.has_key("fmt_type"):
+ kwargs["format"] = getFormat(kwargs.pop("fmt_type"), installer=self.installer,
+ mountpoint=kwargs.pop("mountpoint", None))
+
+ if kwargs.has_key("disks"):
+ parents = kwargs.pop("disks")
+ if isinstance(parents, Device):
+ kwargs["parents"] = [parents]
+ else:
+ kwargs["parents"] = parents
+
+ if kwargs.has_key("name"):
+ name = kwargs.pop("name")
+ else:
+ name = "req%d" % self.nextID
+
+ return PartitionDevice(self.installer, name, *args, **kwargs)
+
+ def newVG(self, *args, **kwargs):
+ """ Return a new LVMVolumeGroupDevice instance. """
+ pvs = kwargs.pop("pvs", [])
+ for pv in pvs:
+ if pv not in self.devices:
+ raise ValueError("pv is not in the device tree")
+
+ if kwargs.has_key("name"):
+ name = kwargs.pop("name")
+ else:
+ # XXX name = self.createSuggestedVGName(self.anaconda.id.network)
+ name = self.createSuggestedVGName(None)
+
+ if name in [d.name for d in self.devices]:
+ raise ValueError("name already in use")
+
+ return LVMVolumeGroupDevice(self.installer, name, pvs, *args, **kwargs)
+
+ def newLV(self, *args, **kwargs):
+ """ Return a new LVMLogicalVolumeDevice instance. """
+ if kwargs.has_key("vg"):
+ vg = kwargs.pop("vg")
+
+ mountpoint = kwargs.pop("mountpoint", None)
+ if kwargs.has_key("fmt_type"):
+ kwargs["format"] = getFormat(kwargs.pop("fmt_type"),
+ installer=self.installer,
+ mountpoint=mountpoint)
+
+ if kwargs.has_key("name"):
+ name = kwargs.pop("name")
+ else:
+ if kwargs.get("format") and kwargs["format"].type == "swap":
+ swap = True
+ else:
+ swap = False
+ name = self.createSuggestedLVName(vg,
+ swap=swap,
+ mountpoint=mountpoint)
+
+ if name in [d.name for d in self.devices]:
+ raise ValueError("Name already in use")
+
+ return LVMLogicalVolumeDevice(self.installer, name, vg, *args, **kwargs)
+
+ def createSuggestedVGName(self, network):
+ """ Return a reasonable, unused VG name. """
+ # try to create a volume group name incorporating the hostname
+ #hn = network.hostname # XXX
+ hn = "%s.localdomain" % PROCUCT_SNAME
+ vgnames = [vg.name for vg in self.vgs]
+ if hn is not None and hn != '':
+ if hn == 'localhost' or hn == 'localhost.localdomain':
+ vgtemplate = "VolGroup"
+ elif hn.find('.') != -1:
+ hn = safeLvmName(hn)
+ vgtemplate = "vg_%s" % (hn.split('.')[0].lower(),)
+ else:
+ hn = safeLvmName(hn)
+ vgtemplate = "vg_%s" % (hn.lower(),)
+ else:
+ vgtemplate = "VolGroup"
+
+ if vgtemplate not in vgnames and \
+ vgtemplate not in lvm.lvm_vg_blacklist:
+ return vgtemplate
+ else:
+ i = 0
+ while 1:
+ tmpname = "%s%02d" % (vgtemplate, i,)
+ if not tmpname in vgnames and \
+ tmpname not in lvm.lvm_vg_blacklist:
+ break
+
+ i += 1
+ if i > 99:
+ tmpname = ""
+
+ return tmpname
+
+ def createSuggestedLVName(self, vg, swap=None, mountpoint=None):
+ """ Return a suitable, unused name for a new logical volume. """
+ # FIXME: this is not at all guaranteed to work
+ if mountpoint:
+ # try to incorporate the mountpoint into the name
+ if mountpoint == '/':
+ lvtemplate = 'lv_root'
+ else:
+ tmp = safeLvmName(mountpoint)
+ lvtemplate = "lv_%s" % (tmp,)
+ else:
+ if swap:
+ if len([s for s in self.swaps if s in vg.lvs]):
+ idx = len([s for s in self.swaps if s in vg.lvs])
+ while True:
+ lvtemplate = "lv_swap%02d" % idx
+ if lvtemplate in [lv.lvname for lv in vg.lvs]:
+ idx += 1
+ else:
+ break
+ else:
+ lvtemplate = "lv_swap"
+ else:
+ idx = len(vg.lvs)
+ while True:
+ lvtemplate = "LogVol%02d" % idx
+ if lvtemplate in [l.lvname for l in vg.lvs]:
+ idx += 1
+ else:
+ break
+
+ return lvtemplate
+
+ def deviceImmutable(self, device):
+ """ Return any reason the device cannot be modified/removed.
+
+ Return False if the device can be removed.
+
+ Devices that cannot be removed include:
+
+ - protected partitions
+ - devices that are part of an md array or lvm vg
+ - extended partition containing logical partitions that
+ meet any of the above criteria
+
+ """
+ if not isinstance(device, Device):
+ raise ValueError("arg1 (%s) must be a Device instance" % device)
+
+ if device.name in self.protectedDisks:
+ return _("This partition is holding the data for the hard "
+ "drive install.")
+ elif device.format.type == "mdmember":
+ for array in self.mdarrays:
+ if array.dependsOn(device):
+ if array.minor is not None:
+ return _("This device is part of the RAID "
+ "device %s.") % (array.path,)
+ else:
+ return _("This device is part of a RAID device.")
+ elif device.format.type == "lvmpv":
+ for vg in self.vgs:
+ if vg.dependsOn(device):
+ if vg.name is not None:
+ return _("This device is part of the LVM "
+ "volume group '%s'.") % (vg.name,)
+ else:
+ return _("This device is part of a LVM volume "
+ "group.")
+ elif device.format.type == "luks":
+ try:
+ luksdev = self.devicetree.getChildren(device)[0]
+ except IndexError:
+ pass
+ else:
+ return self.deviceImmutable(luksdev)
+ elif isinstance(device, PartitionDevice) and device.isExtended:
+ reasons = {}
+ for dep in self.deviceDeps(device):
+ reason = self.deviceImmutable(dep)
+ if reason:
+ reasons[dep.path] = reason
+ if reasons:
+ msg = _("This device is an extended partition which "
+ "contains logical partitions that cannot be "
+ "deleted:\n\n")
+ for dev in reasons:
+ msg += "%s: %s" % (dev, reasons[dev])
+ return msg
+
+ for i in self.devicetree.immutableDevices:
+ if i[0] == device.name:
+ return i[1]
+
+ return False
+
+
+class FSSet(object):
+ """ A class to represent a set of filesystems. """
+ def __init__(self, installer):
+ self.installer = installer
+ self.devicetree = installer.ds.storage.devicetree
+ self.cryptTab = None
+ self.blkidTab = None
+ self.origFStab = None
+ self.active = False
+ self._dev = None
+ self._devpts = None
+ self._sysfs = None
+ self._proc = None
+ self._devshm = None
+
+ @property
+ def sysfs(self):
+ if not self._sysfs:
+ self._sysfs = NoDevice(format=getFormat("sysfs",
+ device="sys",
+ mountpoint="/sys"))
+ return self._sysfs
+
+ @property
+ def dev(self):
+ if not self._dev:
+ self._dev = DirectoryDevice("/dev", format=getFormat("bind",
+ device="/dev",
+ mountpoint="/dev",
+ exists=True),
+ exists=True)
+
+ return self._dev
+
+ @property
+ def devpts(self):
+ if not self._devpts:
+ self._devpts = NoDevice(format=getFormat("devpts",
+ device="devpts",
+ mountpoint="/dev/pts"))
+ return self._devpts
+
+ @property
+ def proc(self):
+ if not self._proc:
+ self._proc = NoDevice(format=getFormat("proc",
+ device="proc",
+ mountpoint="/proc"))
+ return self._proc
+
+ @property
+ def devshm(self):
+ if not self._devshm:
+ self._devshm = NoDevice(format=getFormat("tmpfs",
+ device="tmpfs",
+ mountpoint="/dev/shm"))
+ return self._devshm
+
+ @property
+ def devices(self):
+ devices = self.devicetree.devices.values()
+ devices.sort(key=lambda d: d.path)
+ return devices
+
+ @property
+ def mountpoints(self):
+ filesystems = {}
+ for device in self.devices:
+ if device.format.mountable and device.format.mountpoint:
+ filesystems[device.format.mountpoint] = device
+ return filesystems
+
+ def _parseOneLine(self, (devspec, mountpoint, fstype, options, dump, passno)):
+ # find device in the tree
+ device = self.devicetree.resolveDevice(devspec,
+ cryptTab=self.cryptTab,
+ blkidTab=self.blkidTab)
+ if device:
+ # fall through to the bottom of this block
+ pass
+ elif devspec.startswith("/dev/loop"):
+ # FIXME: create devices.LoopDevice
+ self.installer.log.warning("completely ignoring your loop mount")
+ elif ":" in devspec:
+ # NFS -- preserve but otherwise ignore
+ device = NFSDevice(devspec,
+ format=getFormat(fstype,
+ device=devspec))
+ elif devspec.startswith("/") and fstype == "swap":
+ # swap file
+ device = FileDevice(devspec,
+ parents=get_containing_device(devspec, self.devicetree),
+ format=getFormat(fstype,
+ device=devspec,
+ exists=True),
+ exists=True)
+ elif fstype == "bind" or "bind" in options:
+ # bind mount... set fstype so later comparison won't
+ # turn up false positives
+ fstype = "bind"
+ device = FileDevice(devspec,
+ parents=get_containing_device(devspec, self.devicetree),
+ exists=True)
+ device.format = getFormat("bind",
+ device=device.path,
+ exists=True)
+ elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts"):
+ # drop these now -- we'll recreate later
+ return None
+ else:
+ # nodev filesystem -- preserve or drop completely?
+ format = getFormat(fstype)
+ if devspec == "none" or \
+ isinstance(format, get_device_format_class("nodev")):
+ device = NoDevice(format)
+ else:
+ device = StorageDevice(devspec)
+
+ if device is None:
+ self.installer.log.error("failed to resolve %s (%s) from fstab" % (devspec,
+ fstype))
+ return None
+
+ # make sure, if we're using a device from the tree, that
+ # the device's format we found matches what's in the fstab
+ fmt = getFormat(fstype, device=device.path)
+ if fmt.type != device.format.type:
+ self.installer.log.warning("scanned format (%s) differs from fstab "
+ "format (%s)" % (device.format.type, fstype))
+
+ if device.format.mountable:
+ device.format.mountpoint = mountpoint
+ device.format.mountopts = options
+
+ # is this useful?
+ try:
+ device.format.options = options
+ except AttributeError:
+ pass
+
+ return device
+
+ def parseFSTab(self, chroot=""):
+ """ parse /etc/fstab
+
+ preconditions:
+ all storage devices have been scanned, including filesystems
+ postconditions:
+
+ FIXME: control which exceptions we raise
+
+ XXX do we care about bind mounts?
+ how about nodev mounts?
+ loop mounts?
+ """
+ if not chroot or not os.path.isdir(chroot):
+ chroot = ""
+
+ path = "%s/etc/fstab" % chroot
+ if not os.access(path, os.R_OK):
+ # XXX should we raise an exception instead?
+ self.installer.log.info("cannot open %s for read" % path)
+ return
+
+ blkidTab = BlkidTab(self.installer, chroot=chroot)
+ try:
+ blkidTab.parse()
+ self.installer.log.debug("blkid.tab devs: %s" % blkidTab.devices.keys())
+ except Exception as e:
+ self.installer.log.info("error parsing blkid.tab: %s" % e)
+ blkidTab = None
+
+ cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot)
+ try:
+ cryptTab.parse(chroot=chroot)
+ self.installer.log.debug("crypttab maps: %s" % cryptTab.mappings.keys())
+ except Exception as e:
+ self.installer.log.info("error parsing crypttab: %s" % e)
+ cryptTab = None
+
+ self.blkidTab = blkidTab
+ self.cryptTab = cryptTab
+
+ with open(path) as f:
+ self.installer.log.debug("parsing %s" % path)
+
+ lines = f.readlines()
+
+ # save the original file
+ self.origFStab = ''.join(lines)
+
+ for line in lines:
+ # strip off comments
+ (line, pound, comment) = line.partition("#")
+ fields = line.split()
+
+ if not 4 <= len(fields) <= 6:
+ continue
+ elif len(fields) == 4:
+ fields.extend([0, 0])
+ elif len(fields) == 5:
+ fields.append(0)
+
+ (devspec, mountpoint, fstype, options, dump, passno) = fields
+
+ try:
+ device = self._parseOneLine((devspec, mountpoint, fstype, options, dump, passno))
+ except Exception as e:
+ raise Exception("fstab entry %s is malformed: %s" % (devspec, e))
+
+ if not device:
+ continue
+
+ if device not in self.devicetree.devices.values():
+ self.devicetree._addDevice(device)
+
+ def fsFreeSpace(self, chroot='/'):
+ space = []
+ for device in self.devices:
+ if not device.format.mountable or \
+ not device.format.status:
+ continue
+
+ path = "%s/%s" % (chroot, device.format.mountpoint)
+ try:
+ space.append((device.format.mountpoint,
+ isys.pathSpaceAvailable(path)))
+ except SystemError:
+ self.installer.log.error("failed to calculate free space for %s" % (device.format.mountpoint,))
+
+ space.sort(key=lambda s: s[1])
+ return space
+
+ def mtab(self):
+ format = "%s %s %s %s 0 0\n"
+ mtab = ""
+ devices = self.mountpoints.values() + self.swapDevices
+ devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
+ devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+ for device in devices:
+ if not device.format.status:
+ continue
+ if not device.format.mountable:
+ continue
+ if device.format.mountpoint:
+ options = device.format.mountopts
+ if options:
+ options = options.replace("defaults,", "")
+ options = options.replace("defaults", "")
+
+ if options:
+ options = "rw," + options
+ else:
+ options = "rw"
+ mtab = mtab + format % (device.path,
+ device.format.mountpoint,
+ device.format.type,
+ options)
+ return mtab
+
+ def turnOnSwap(self):
+ intf = self.installer.intf
+ for device in self.swapDevices:
+ try:
+ device.setup()
+ device.format.setup()
+ except SuspendError:
+ if intf:
+ msg = _("The swap device:\n\n %s\n\n"
+ "in your /etc/fstab file is currently in "
+ "use as a software suspend device, "
+ "which means your system is hibernating. "
+ "If you are performing a new install, "
+ "make sure the installer is set "
+ "to format all swap devices.") \
+ % device.path
+ intf.messageWindow(_("Error"), msg)
+ sys.exit(0)
+ except DeviceError as msg:
+ if intf:
+ err = _("Error enabling swap device %s: %s\n\n"
+ "This most likely means this swap "
+ "device has not been initialized.\n\n"
+ "Press OK to exit the installer.") % \
+ (device.path, msg)
+ intf.messageWindow(_("Error"), err)
+ sys.exit(0)
+
+ def mountFilesystems(self, installer, raiseErrors=None, readOnly=None,
+ skipRoot=False):
+ intf = installer.intf
+ devices = self.mountpoints.values() + self.swapDevices
+ devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
+ devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+
+ for device in devices:
+ if not device.format.mountable or not device.format.mountpoint:
+ continue
+
+ if skipRoot and device.format.mountpoint == "/":
+ continue
+
+ options = device.format.options
+ if "noauto" in options.split(","):
+ continue
+
+ try:
+ device.setup()
+ except Exception as msg:
+ # FIXME: need an error popup
+ continue
+
+ if readOnly:
+ options = "%s,%s" % (options, readOnly)
+
+ try:
+ device.format.setup(options=options,
+ chroot=installer.rootPath)
+ except OSError as (num, msg):
+ if intf:
+ if num == errno.EEXIST:
+ intf.messageWindow(_("Invalid mount point"),
+ _("An error occurred when trying "
+ "to create %s. Some element of "
+ "this path is not a directory. "
+ "This is a fatal error and the "
+ "install cannot continue.\n\n"
+ "Press <Enter> to exit the "
+ "installer.")
+ % (device.format.mountpoint,))
+ else:
+ intf.messageWindow(_("Invalid mount point"),
+ _("An error occurred when trying "
+ "to create %s: %s. This is "
+ "a fatal error and the install "
+ "cannot continue.\n\n"
+ "Press <Enter> to exit the "
+ "installer.")
+ % (device.format.mountpoint, msg))
+ self.installer.log.error("OSError: (%d) %s" % (num, msg) )
+ sys.exit(0)
+ except SystemError as (num, msg):
+ if raiseErrors:
+ raise
+ if intf and not device.format.linuxNative:
+ ret = intf.messageWindow(_("Unable to mount filesystem"),
+ _("An error occurred mounting "
+ "device %s as %s. You may "
+ "continue installation, but "
+ "there may be problems.") %
+ (device.path,
+ device.format.mountpoint),
+ type="custom",
+ custom_icon="warning",
+ custom_buttons=[_("_Exit installer"),
+ _("_Continue")])
+
+ if ret == 0:
+ sys.exit(0)
+ else:
+ continue
+
+ self.installer.log.error("SystemError: (%d) %s" % (num, msg) )
+ sys.exit(0)
+ except FSError as msg:
+ if intf:
+ intf.messageWindow(_("Unable to mount filesystem"),
+ _("An error occurred mounting "
+ "device %s as %s: %s. This is "
+ "a fatal error and the install "
+ "cannot continue.\n\n"
+ "Press <Enter> to exit the "
+ "installer.")
+ % (device.path,
+ device.format.mountpoint,
+ msg))
+ self.installer.log.error("FSError: %s" % msg)
+ sys.exit(0)
+
+ self.active = True
+
+ def umountFilesystems(self, instPath, ignoreErrors=True, swapoff=True):
+ devices = self.mountpoints.values() + self.swapDevices
+ devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc])
+ devices.sort(key=lambda d: getattr(d.format, "mountpoint", None))
+ devices.reverse()
+ for device in devices:
+ if not device.format.mountable and \
+ (device.format.type != "swap" or swapoff):
+ continue
+
+ device.format.teardown()
+ device.teardown()
+
+ self.active = False
+
+ def createSwapFile(self, rootPath, device, size):
+ """ Create and activate a swap file under rootPath. """
+ filename = "/SWAP"
+ count = 0
+ basedir = os.path.normpath("%s/%s" % (rootPath,
+ device.format.mountpoint))
+ while os.path.exists("%s/%s" % (basedir, filename)) or \
+ self.devicetree.getDeviceByName(filename):
+ file = os.path.normpath("%s/%s" % (basedir, filename))
+ count += 1
+ filename = "/SWAP-%d" % count
+
+ dev = FileDevice(filename,
+ size=size,
+ parents=[device],
+ format=getFormat("swap", device=filename))
+ dev.create()
+ dev.setup()
+ dev.format.create()
+ dev.format.setup()
+ # nasty, nasty
+ self.devicetree._addDevice(dev)
+
+ def mkDevRoot(self, instPath):
+ root = self.rootDevice
+ dev = "%s/%s" % (instPath, root.path)
+ if not os.path.exists("%s/dev/root" %(instPath,)) and os.path.exists(dev):
+ rdev = os.stat(dev).st_rdev
+ os.mknod("%s/dev/root" % (instPath,), stat.S_IFBLK | 0600, rdev)
+
+ @property
+ def swapDevices(self):
+ swaps = []
+ for device in self.devices:
+ if device.format.type == "swap":
+ swaps.append(device)
+ return swaps
+
+ @property
+ def rootDevice(self):
+ for device in self.devices:
+ try:
+ mountpoint = device.format.mountpoint
+ except AttributeError:
+ mountpoint = None
+
+ if mountpoint == "/":
+ return device
+
+ @property
+ def migratableDevices(self):
+ """ List of devices whose filesystems can be migrated. """
+ migratable = []
+ for device in self.devices:
+ if device.format.migratable and device.format.exists:
+ migratable.append(device)
+
+ return migratable
+
+ def write(self, instPath):
+ """ write out all config files based on the set of filesystems """
+ # /etc/fstab
+ fstab_path = os.path.normpath("%s/etc/fstab" % instPath)
+ fstab = self.fstab()
+ open(fstab_path, "w").write(fstab)
+
+ # /etc/crypttab
+ crypttab_path = os.path.normpath("%s/etc/crypttab" % instPath)
+ crypttab = self.crypttab()
+ open(crypttab_path, "w").write(crypttab)
+
+ # /etc/mdadm.conf
+ mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % instPath)
+ mdadm_conf = self.mdadmConf()
+ open(mdadm_path, "w").write(mdadm_conf)
+
+ def crypttab(self):
+ # if we are upgrading, do we want to update crypttab?
+ # gut reaction says no, but plymouth needs the names to be very
+ # specific for passphrase prompting
+ if not self.cryptTab:
+ self.cryptTab = CryptTab(self.devicetree)
+ self.cryptTab.populate()
+
+ devices = self.mountpoints.values() + self.swapDevices
+
+ # prune crypttab -- only mappings required by one or more entries
+ for name in self.cryptTab.mappings.keys():
+ keep = False
+ mapInfo = self.cryptTab[name]
+ cryptoDev = mapInfo['device']
+ for device in devices:
+ if device == cryptoDev or device.dependsOn(cryptoDev):
+ keep = True
+ break
+
+ if not keep:
+ del self.cryptTab.mappings[name]
+
+ return self.cryptTab.crypttab()
+
+ def mdadmConf(self):
+ """ Return the contents of mdadm.conf. """
+ arrays = self.devicetree.getDevicesByType("mdarray")
+ conf = ""
+ devices = self.mountpoints.values() + self.swapDevices
+ for array in arrays:
+ writeConf = False
+ for device in devices:
+ if device == array or device.dependsOn(array):
+ writeConf = True
+ break
+
+ if writeConf:
+ conf += array.mdadmConfEntry
+
+ return conf
+
+ def fstab (self):
+ format = "%-23s %-23s %-7s %-15s %d %d\n"
+ fstab = """
+#
+# /etc/fstab
+# Created by pomona on %s
+#
+# Accessible filesystems, by reference, are maintained under '/dev/disk'
+# See man pages fstab(5), findfs(8), mount(8) and/or vol_id(8) for more info
+#
+""" % time.asctime()
+
+ devices = self.mountpoints.values() + self.swapDevices
+ devices.extend([self.devshm, self.devpts, self.sysfs, self.proc])
+ netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice)
+ for device in devices:
+ # why the hell do we put swap in the fstab, anyway?
+ if not device.format.mountable and device.format.type != "swap":
+ continue
+
+ fstype = device.format.type
+ if fstype == "swap":
+ mountpoint = "swap"
+ options = device.format.options
+ else:
+ mountpoint = device.format.mountpoint
+ options = device.format.mountopts
+ if not mountpoint:
+ self.installer.log.warning("%s filesystem on %s has no mountpoint" % \
+ (fstype, device.path))
+ continue
+
+ options = options or "defaults"
+ for netdev in netdevs:
+ if device.dependsOn(netdev):
+ options = options + ",_netdev"
+ break
+ devspec = device.fstabSpec
+ dump = device.format.dump
+ if device.format.check and mountpoint == "/":
+ passno = 1
+ elif device.format.check:
+ passno = 2
+ else:
+ passno = 0
+ fstab = fstab + device.fstabComment
+ fstab = fstab + format % (devspec, mountpoint, fstype,
+ options, dump, passno)
+ return fstab
+
+class PartSpec(object):
+ def __init__(self, mountpoint=None, fstype=None, size=None, maxSize=None,
+ grow=False, asVol=False, weight=0):
+ self.mountpoint = mountpoint
+ self.fstype = fstype
+ self.size = size
+ self.maxSize = maxSize
+ self.grow = grow
+ self.asVol = asVol
+ self.weight = weight
+
+
+class BlkidTab(object):
+ """ Dictionary-like interface to blkid.tab with device path keys """
+ def __init__(self, installer, chroot=""):
+ self.installer = installer
+ self.chroot = chroot
+ self.devices = {}
+
+ def parse(self):
+ path = "%s/etc/blkid/blkid.tab" % self.chroot
+ self.installer.log.debug("parsing %s" % path)
+ with open(path) as f:
+ for line in f.readlines():
+ # this is pretty ugly, but an XML parser is more work than
+ # is justifiable for this purpose
+ if not line.startswith("<device "):
+ continue
+
+ line = line[len("<device "):-len("</device>\n")]
+ (data, sep, device) = line.partition(">")
+ if not device:
+ continue
+
+ self.devices[device] = {}
+ for pair in data.split():
+ try:
+ (key, value) = pair.split("=")
+ except ValueError:
+ continue
+
+ self.devices[device][key] = value[1:-1] # strip off quotes
+
+ def __getitem__(self, key):
+ return self.devices[key]
+
+ def get(self, key, default=None):
+ return self.devices.get(key, default)
+
+
+class CryptTab(object):
+ """ Dictionary-like interface to crypttab entries with map name keys """
+ def __init__(self, devicetree, blkidTab=None, chroot=""):
+ self.devicetree = devicetree
+ self.blkidTab = blkidTab
+ self.chroot = chroot
+ self.mappings = {}
+
+ def parse(self, chroot=""):
+ """ Parse /etc/crypttab from an existing installation. """
+ if not chroot or not os.path.isdir(chroot):
+ chroot = ""
+
+ path = "%s/etc/crypttab" % chroot
+ log.debug("parsing %s" % path)
+ with open(path) as f:
+ if not self.blkidTab:
+ try:
+ self.blkidTab = BlkidTab(chroot=chroot)
+ self.blkidTab.parse()
+ except Exception:
+ self.blkidTab = None
+
+ for line in f.readlines():
+ (line, pound, comment) = line.partition("#")
+ fields = line.split()
+ if not 2 <= len(fields) <= 4:
+ continue
+ elif len(fields) == 2:
+ fields.extend(['none', ''])
+ elif len(fields) == 3:
+ fields.append('')
+
+ (name, devspec, keyfile, options) = fields
+
+ # resolve devspec to a device in the tree
+ device = self.devicetree.resolveDevice(devspec,
+ blkidTab=self.blkidTab)
+ if device:
+ self.mappings[name] = {"device": device,
+ "keyfile": keyfile,
+ "options": options}
+
+ def populate(self):
+ """ Populate the instance based on the device tree's contents. """
+ for device in self.devicetree.devices.values():
+ # XXX should we put them all in there or just the ones that
+ # are part of a device containing swap or a filesystem?
+ #
+ # Put them all in here -- we can filter from FSSet
+ if device.format.type != "luks":
+ continue
+
+ key_file = device.format.keyFile
+ if not key_file:
+ key_file = "none"
+
+ options = device.format.options
+ if not options:
+ options = ""
+
+ self.mappings[device.format.mapName] = {"device": device,
+ "keyfile": key_file,
+ "options": options}
+
+ def crypttab(self):
+ """ Write out /etc/crypttab """
+ crypttab = ""
+ for name in self.mappings:
+ entry = self[name]
+ crypttab += "%s UUID=%s %s %s\n" % (name,
+ entry['device'].format.uuid,
+ entry['keyfile'],
+ entry['options'])
+ return crypttab
+
+ def __getitem__(self, key):
+ return self.mappings[key]
+
+ def get(self, key, default=None):
+ return self.mappings.get(key, default)
--- /dev/null
+#!/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
--- /dev/null
+#
+# crypto.py
+#
+# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author(s): Dave Lehman <dlehman@redhat.com>
+# Martin Sivak <msivak@redhat.com>
+#
+
+import os
+from pycryptsetup import CryptSetup
+
+from ..errors import *
+
+def askyes(question):
+ return True
+
+def dolog(priority, text):
+ pass
+
+def is_luks(device):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.isLuks(device)
+
+def luks_uuid(device):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.luksUUID(device).strip()
+
+def luks_status(name):
+ """True means active, False means inactive (or non-existent)"""
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.luksStatus(name)!=0
+
+def luks_format(device,
+ passphrase=None, key_file=None,
+ cipher=None, key_size=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ key_file_unlink = False
+
+ if passphrase:
+ key_file = cs.prepare_passphrase_file(passphrase)
+ key_file_unlink = True
+ elif key_file and os.path.isfile(key_file):
+ pass
+ else:
+ raise ValueError("luks_format requires either a passphrase or a key file")
+
+ #None is not considered as default value and pycryptsetup doesn't accept it
+ #so we need to filter out all Nones
+ kwargs = {}
+ kwargs["device"] = device
+ if cipher: kwargs["cipher"] = cipher
+ if key_file: kwargs["keyfile"] = key_file
+ if key_size: kwargs["keysize"] = key_size
+
+ rc = cs.luksFormat(**kwargs)
+ if key_file_unlink: os.unlink(key_file)
+
+ if rc:
+ raise CryptoError("luks_format failed for '%s'" % device)
+
+def luks_open(device, name, passphrase=None, key_file=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ key_file_unlink = False
+
+ if passphrase:
+ key_file = cs.prepare_passphrase_file(passphrase)
+ key_file_unlink = True
+ elif key_file and os.path.isfile(key_file):
+ pass
+ else:
+ raise ValueError("luks_open requires either a passphrase or a key file")
+
+ rc = cs.luksOpen(device = device, name = name, keyfile = key_file)
+ if key_file_unlink: os.unlink(key_file)
+ if rc:
+ raise CryptoError("luks_open failed for %s (%s)" % (device, name))
+
+def luks_close(name):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ rc = cs.luksClose(name)
+ if rc:
+ raise CryptoError("luks_close failed for %s" % name)
+
+def luks_add_key(device,
+ new_passphrase=None, new_key_file=None,
+ passphrase=None, key_file=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.addKey(device, new_passphrase, new_key_file, passphrase, key_file)
+
+
+def luks_remove_key(device,
+ del_passphrase=None, del_key_file=None,
+ passphrase=None, key_file=None):
+ cs = CryptSetup(yesDialog = askyes, logFunc = dolog)
+ return cs.removeKey(device, del_passphrase, del_key_file, passphrase, key_file)
--- /dev/null
+#!/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
--- /dev/null
+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
--- /dev/null
+#/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)
--- /dev/null
+#!/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)
--- /dev/null
+#!/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
--- /dev/null
+#!/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()
--- /dev/null
+#!/usr/bin/python
+
+import os
+import tempfile
+
+import isys
+
+import util
+
+from ..errors import *
+from . import DeviceFormat, register_device_format
+
+import gettext
+_ = lambda x: gettext.ldgettext("pomona", x)
+
+fs_configs = {}
+
+def get_kernel_filesystems():
+ fs_list = []
+ for line in open("/proc/filesystems").readlines():
+ fs_list.append(line.split()[-1])
+ return fs_list
+
+global kernel_filesystems
+kernel_filesystems = get_kernel_filesystems()
+
+class FS(DeviceFormat):
+ """ Filesystem class. """
+ _type = "Abstract Filesystem Class" # fs type name
+ _mountType = None # like _type but for passing to mount
+ _name = None
+ _mkfs = "" # mkfs utility
+ _modules = [] # kernel modules required for support
+ _resizefs = "" # resize utility
+ _labelfs = "" # labeling utility
+ _fsck = "" # fs check utility
+ _migratefs = "" # fs migration utility
+ _defaultFormatOptions = [] # default options passed to mkfs
+ _defaultMountOptions = ["defaults"] # default options passed to mount
+ _defaultLabelOptions = []
+ _defaultCheckOptions = []
+ _defaultMigrateOptions = []
+ _migrationTarget = None
+ lostAndFoundContext = None
+
+ def __init__(self, installer, *args, **kwargs):
+ """ Create a FS instance.
+
+ Keyword Args:
+
+ device -- path to the device containing the filesystem
+ mountpoint -- the filesystem's mountpoint
+ label -- the filesystem label
+ uuid -- the filesystem UUID
+ mountopts -- mount options for the filesystem
+ size -- the filesystem's size in MiB
+ exists -- indicates whether this is an existing filesystem
+
+ """
+ if self.__class__ is FS:
+ raise TypeError("FS is an abstract class.")
+
+ self.installer = installer
+
+ DeviceFormat.__init__(self, self.installer, *args, **kwargs)
+ # TODO: fsprofiles and other ways to add format args
+ self.mountpoint = kwargs.get("mountpoint")
+ self.mountopts = kwargs.get("mountopts")
+ self.label = kwargs.get("label")
+
+ # filesystem size does not necessarily equal device size
+ self._size = kwargs.get("size")
+ self._mountpoint = None # the current mountpoint when mounted
+ if self.exists:
+ self._size = self._getExistingSize()
+
+ self._targetSize = self._size
+
+ if self.supported:
+ self.loadModule()
+
+ def _setTargetSize(self, newsize):
+ """ Set a target size for this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if newsize is None:
+ # unset any outstanding resize request
+ self._targetSize = None
+ return
+
+ if not self.minSize < newsize < self.maxSize:
+ raise ValueError("invalid target size request")
+
+ self._targetSize = newsize
+
+ def _getTargetSize(self):
+ """ Get this filesystem's target size. """
+ return self._targetSize
+
+ targetSize = property(_getTargetSize, _setTargetSize,
+ doc="Target size for this filesystem")
+
+ def _getSize(self):
+ """ Get this filesystem's size. """
+ size = self._size
+ if self.resizable and self.targetSize != size:
+ size = self.targetSize
+ return size
+
+ size = property(_getSize, doc="This filesystem's size, accounting "
+ "for pending changes")
+
+ def _getExistingSize(self):
+ """ Determine the size of this filesystem. Filesystem must
+ exist.
+ """
+ size = 0
+
+ if self.mountable:
+ origMountPoint = self._mountpoint
+
+ tmppath = tempfile.mkdtemp(prefix='getsize-', dir='/tmp')
+ self.mount(mountpoint=tmppath, options="ro")
+ buf = os.statvfs(tmppath)
+ self.unmount()
+ os.rmdir(tmppath)
+
+ self._mountpoint = origMountPoint
+
+ size = (buf.f_frsize * buf.f_blocks) / 1024.0 / 1024.0
+
+ return size
+
+ @property
+ def currentSize(self):
+ """ The filesystem's current actual size. """
+ size = 0
+ if self.exists:
+ size = self._size
+ return float(size)
+
+ def _getFormatOptions(self, options=None):
+ argv = []
+ if options and isinstance(options, list):
+ argv.extend(options)
+ argv.extend(self.defaultFormatOptions)
+ argv.append(self.device)
+ return argv
+
+ def doFormat(self, *args, **kwargs):
+ """ Create the filesystem.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ intf -- InstallInterface instance
+ options -- list of options to pass to mkfs
+
+ """
+ intf = kwargs.get("intf")
+ options = kwargs.get("options")
+
+ if self.exists:
+ raise FormatCreateError("filesystem already exists", self.device)
+
+ if not self.formattable:
+ return
+
+ if not self.mkfsProg:
+ return
+
+ if self.exists:
+ return
+
+ if not os.path.exists(self.device):
+ raise FormatCreateError("device does not exist", self.device)
+
+ argv = self._getFormatOptions(options=options)
+
+ self.installer.window = None
+ self.installer.window = self.installer.intf.progressWindow(_("Formatting"),
+ _("Creating filesystem on %s...") % (self.device,),
+ 100, pulse = True)
+
+ try:
+ rc = util.execWithPulseProgress(self.mkfsProg,
+ argv,
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress=w)
+ except Exception as e:
+ raise FormatCreateError(e, self.device)
+ finally:
+ if self.installer.window:
+ self.installer.window.pop()
+
+ if rc:
+ raise FormatCreateError("format failed: %s" % rc, self.device)
+
+ self.exists = True
+ self.notifyKernel()
+
+ def doMigrate(self, intf=None):
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.migratable or not self.migrate:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ # if journal already exists skip
+ if isys.ext2HasJournal(self.device):
+ self.installer.log.info("Skipping migration of %s, has a journal already." % self.device)
+ return
+
+ argv = self._defaultMigrateOptions[:]
+ argv.append(self.device)
+ try:
+ rc = util.execWithRedirect(self.migratefsProg,
+ argv,
+ stdout = "/dev/tty5",
+ stderr = "/dev/tty5",
+ searchPath = 1)
+ except Exception as e:
+ raise FSMigrateError("filesystem migration failed: %s" % e, self.device)
+
+ if rc:
+ raise FSMigrateError("filesystem migration failed: %s" % rc, self.device)
+
+ # the other option is to actually replace this instance with an
+ # instance of the new filesystem type.
+ self._type = self.migrationTarget
+
+ @property
+ def resizeArgs(self):
+ argv = [self.device, "%d" % (self.targetSize,)]
+ return argv
+
+ def doResize(self, *args, **kwargs):
+ """ Resize this filesystem to new size @newsize.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ intf -- InstallInterface instance
+
+ """
+ intf = kwargs.get("intf")
+
+ if not self.exists:
+ raise FSResizeError("filesystem does not exist", self.device)
+
+ if not self.resizable:
+ raise FSResizeError("filesystem not resizable", self.device)
+
+ if self.targetSize == self.currentSize:
+ return
+
+ if not self.resizefsProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSResizeError("device does not exist", self.device)
+
+ self.doCheck(intf=intf)
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Resizing"),
+ _("Resizing filesystem on %s...")
+ % (self.device,),
+ 100, pulse = True)
+
+ try:
+ rc = util.execWithPulseProgress(self.resizefsProg,
+ self.resizeArgs,
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress=w)
+ except Exception as e:
+ raise FSResizeError(e, self.device)
+ finally:
+ if w:
+ w.pop()
+
+ if rc:
+ raise FSResizeError("resize failed: %s" % rc, self.device)
+
+ # XXX must be a smarter way to do this
+ self._size = self.targetSize
+ self.notifyKernel()
+
+ def _getCheckArgs(self):
+ argv = []
+ argv.extend(self.defaultCheckOptions)
+ argv.append(self.device)
+ return argv
+
+ def doCheck(self, intf=None):
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.fsckProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ w = None
+ if intf:
+ w = intf.progressWindow(_("Checking"),
+ _("Checking filesystem on %s...")
+ % (self.device),
+ 100, pulse = True)
+
+ try:
+ rc = util.execWithPulseProgress(self.fsckProg,
+ self._getCheckArgs(),
+ stdout="/dev/tty5",
+ stderr="/dev/tty5",
+ progress = w)
+ except Exception as e:
+ raise FSError("filesystem check failed: %s" % e)
+ finally:
+ if w:
+ w.pop()
+
+ if rc >= 4:
+ raise FSError("filesystem check failed: %s" % rc)
+
+ def loadModule(self):
+ """Load whatever kernel module is required to support this filesystem."""
+ global kernel_filesystems
+
+ if not self._modules or self.mountType in kernel_filesystems:
+ return
+
+ for module in self._modules:
+ try:
+ rc = util.execWithRedirect("modprobe", [module],
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ searchPath=1)
+ except Exception as e:
+ self.installer.log.error("Could not load kernel module %s: %s" % (module, e))
+ self._supported = False
+ return
+
+ if rc:
+ self.installer.log.error("Could not load kernel module %s" % module)
+ self._supported = False
+ return
+
+ # If we successfully loaded a kernel module, for this filesystem, we
+ # also need to update the list of supported filesystems.
+ kernel_filesystems = get_kernel_filesystems()
+
+ def mount(self, *args, **kwargs):
+ """ Mount this filesystem.
+
+ Arguments:
+
+ None
+
+ Keyword Arguments:
+
+ options -- mount options (overrides all other option strings)
+ chroot -- prefix to apply to mountpoint
+ mountpoint -- mountpoint (overrides self.mountpoint)
+ """
+ options = kwargs.get("options", "")
+ chroot = kwargs.get("chroot", "/")
+ mountpoint = kwargs.get("mountpoint")
+
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not mountpoint:
+ mountpoint = self.mountpoint
+
+ if not mountpoint:
+ raise FSError("no mountpoint given")
+
+ if self.status:
+ return
+
+ if not isinstance(self, NoDevFS) and not os.path.exists(self.device):
+ raise FSError("device %s does not exist" % self.device)
+
+ # XXX os.path.join is FUBAR:
+ #
+ # os.path.join("/mnt/foo", "/") -> "/"
+ #
+ #mountpoint = os.path.join(chroot, mountpoint)
+ mountpoint = os.path.normpath("%s/%s" % (chroot, mountpoint))
+ util.mkdirChain(mountpoint)
+
+ # passed in options override default options
+ if not options or not isinstance(options, str):
+ options = self.options
+
+ try:
+ rc = isys.mount(self.device, mountpoint,
+ fstype=self.mountType,
+ options=options,
+ bindMount=isinstance(self, BindFS))
+ except Exception as e:
+ raise FSError("mount failed: %s" % e)
+
+ if rc:
+ raise FSError("mount failed: %s" % rc)
+
+ self._mountpoint = mountpoint
+
+ def unmount(self):
+ """ Unmount this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self._mountpoint:
+ # not mounted
+ return
+
+ if not os.path.exists(self._mountpoint):
+ raise FSError("mountpoint does not exist")
+
+ rc = isys.umount(self._mountpoint, removeDir = False)
+ if rc:
+ raise FSError("umount failed")
+
+ self._mountpoint = None
+
+ def _getLabelArgs(self, label):
+ argv = []
+ argv.extend(self.defaultLabelOptions)
+ argv.extend([self.device, label])
+ return argv
+
+ def writeLabel(self, label):
+ """ Create a label for this filesystem. """
+ if not self.exists:
+ raise FSError("filesystem has not been created")
+
+ if not self.labelfsProg:
+ return
+
+ if not os.path.exists(self.device):
+ raise FSError("device does not exist")
+
+ argv = self._getLabelArgs(label)
+ rc = util.execWithRedirect(self.labelfsProg,
+ argv,
+ stderr="/dev/tty5",
+ searchPath=1)
+ if rc:
+ raise FSError("label failed")
+
+ self.label = label
+ self.notifyKernel()
+
+ @property
+ def isDirty(self):
+ return False
+
+ @property
+ def mkfsProg(self):
+ """ Program used to create filesystems of this type. """
+ return self._mkfs
+
+ @property
+ def fsckProg(self):
+ """ Program used to check filesystems of this type. """
+ return self._fsck
+
+ @property
+ def resizefsProg(self):
+ """ Program used to resize filesystems of this type. """
+ return self._resizefs
+
+ @property
+ def labelfsProg(self):
+ """ Program used to manage labels for this filesystem type. """
+ return self._labelfs
+
+ @property
+ def migratefsProg(self):
+ """ Program used to migrate filesystems of this type. """
+ return self._migratefs
+
+ @property
+ def migrationTarget(self):
+ return self._migrationTarget
+
+ @property
+ def utilsAvailable(self):
+ # we aren't checking for fsck because we shouldn't need it
+ for prog in [self.mkfsProg, self.resizefsProg, self.labelfsProg]:
+ if not prog:
+ continue
+
+ if not filter(lambda d: os.access("%s/%s" % (d, prog), os.X_OK),
+ os.environ["PATH"].split(":")):
+ return False
+
+ return True
+
+ @property
+ def supported(self):
+ return self._supported and self.utilsAvailable
+
+ @property
+ def mountable(self):
+ return (self.mountType in kernel_filesystems) or \
+ (os.access("/sbin/mount.%s" % (self.mountType,), os.X_OK))
+
+ @property
+ def defaultFormatOptions(self):
+ """ Default options passed to mkfs for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultFormatOptions[:]
+
+ @property
+ def defaultMountOptions(self):
+ """ Default options passed to mount for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultMountOptions[:]
+
+ @property
+ def defaultLabelOptions(self):
+ """ Default options passed to labeler for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultLabelOptions[:]
+
+ @property
+ def defaultCheckOptions(self):
+ """ Default options passed to checker for this filesystem type. """
+ # return a copy to prevent modification
+ return self._defaultCheckOptions[:]
+
+ def _getOptions(self):
+ options = ",".join(self.defaultMountOptions)
+ if self.mountopts:
+ # XXX should we clobber or append?
+ options = self.mountopts
+ return options
+
+ def _setOptions(self, options):
+ self.mountopts = options
+
+ options = property(_getOptions, _setOptions)
+
+ @property
+ def migratable(self):
+ """ Can filesystems of this type be migrated? """
+ return bool(self._migratable and self.migratefsProg and
+ filter(lambda d: os.access("%s/%s"
+ % (d, self.migratefsProg,),
+ os.X_OK),
+ os.environ["PATH"].split(":")) and
+ self.migrationTarget)
+
+ def _setMigrate(self, migrate):
+ if not migrate:
+ self._migrate = migrate
+ return
+
+ if self.migratable and self.exists:
+ self._migrate = migrate
+ else:
+ raise ValueError("Cannot set migrate on non-migratable filesystem")
+
+ migrate = property(lambda f: f._migrate, lambda f,m: f._setMigrate(m))
+
+ @property
+ def type(self):
+ _type = self._type
+ if self.migrate:
+ _type = self.migrationTarget
+
+ return _type
+
+ @property
+ def mountType(self):
+ if not self._mountType:
+ self._mountType = self._type
+
+ return self._mountType
+
+ # These methods just wrap filesystem-specific methods in more
+ # generically named methods so filesystems and formatted devices
+ # like swap and LVM physical volumes can have a common API.
+ def create(self, *args, **kwargs):
+ if self.exists:
+ raise FSError("Filesystem already exists")
+
+ DeviceFormat.create(self, *args, **kwargs)
+
+ return self.doFormat(*args, **kwargs)
+
+ def setup(self, *args, **kwargs):
+ """ Mount the filesystem.
+
+ THe filesystem will be mounted at the directory indicated by
+ self.mountpoint.
+ """
+ return self.mount(**kwargs)
+
+ def teardown(self, *args, **kwargs):
+ return self.unmount(*args, **kwargs)
+
+ @property
+ def status(self):
+ # FIXME check /proc/mounts or similar
+ if not self.exists:
+ return False
+ return self._mountpoint is not None
+
+
+class Ext2FS(FS):
+ """ ext2 filesystem. """
+ _type = "ext2"
+ _mkfs = "mke2fs"
+ _modules = ["ext2"]
+ _resizefs = "resize2fs"
+ _labelfs = "e2label"
+ _fsck = "e2fsck"
+ _formattable = True
+ _supported = True
+ _resizable = True
+ _bootable = True
+ _linuxNative = True
+ _maxSize = 8 * 1024 * 1024
+ _minSize = 0
+ _defaultFormatOptions = []
+ _defaultMountOptions = ["defaults"]
+ _defaultCheckOptions = ["-f", "-p", "-C", "0"]
+ _dump = True
+ _check = True
+ _migratable = True
+ _migrationTarget = "ext3"
+ _migratefs = "tune2fs"
+ _defaultMigrateOptions = ["-j"]
+
+ @property
+ def minSize(self):
+ """ Minimum size for this filesystem in MB. """
+ size = self._minSize
+ if self.exists and os.path.exists(self.device):
+ buf = util.execWithCapture(self.resizefsProg,
+ ["-P", self.device],
+ stderr="/dev/tty5")
+ size = None
+ for line in buf.splitlines():
+ if "minimum size of the filesystem:" not in line:
+ continue
+
+ (text, sep, minSize) = line.partition(": ")
+
+ size = int(minSize) / 1024.0
+
+ if size is None:
+ self.installer.log.warning("failed to get minimum size for %s filesystem "
+ "on %s" % (self.mountType, self.device))
+ size = self._minSize
+
+ return size
+
+ @property
+ def isDirty(self):
+ return isys.ext2IsDirty(self.device)
+
+ @property
+ def resizeArgs(self):
+ argv = ["-p", self.device, "%dM" % (self.targetSize,)]
+ return argv
+
+register_device_format(Ext2FS)
+
+
+class Ext3FS(Ext2FS):
+ """ ext3 filesystem. """
+ _type = "ext3"
+ _defaultFormatOptions = ["-t", "ext3"]
+ _migrationTarget = "ext4"
+ _modules = ["ext3"]
+ _defaultMigrateOptions = ["-O", "extents"]
+
+ @property
+ def migratable(self):
+ """ Can filesystems of this type be migrated? """
+ return (flags.cmdline.has_key("ext4migrate") and
+ Ext2FS.migratable)
+
+register_device_format(Ext3FS)
+
+
+class Ext4FS(Ext3FS):
+ """ ext4 filesystem. """
+ _type = "ext4"
+ _bootable = False
+ _defaultFormatOptions = ["-t", "ext4"]
+ _migratable = False
+ _modules = ["ext4"]
+
+register_device_format(Ext4FS)
+
+
+class FATFS(FS):
+ """ FAT filesystem. """
+ _type = "vfat"
+ _mkfs = "mkdosfs"
+ _modules = ["vfat"]
+ _labelfs = "dosfslabel"
+ _fsck = "dosfsck"
+ _formattable = True
+ _maxSize = 1024 * 1024
+ _defaultMountOptions = ["umask=0077", "shortname=winnt"]
+
+register_device_format(FATFS)
+
+
+class BTRFS(FS):
+ """ btrfs filesystem """
+ _type = "btrfs"
+ _mkfs = "mkfs.btrfs"
+ _modules = ["btrfs"]
+ _resizefs = "btrfsctl"
+ _formattable = True
+ _linuxNative = True
+ _bootable = False
+ _maxLabelChars = 256
+ _supported = True
+ _dump = True
+ _check = True
+ _maxSize = 16 * 1024 * 1024
+
+ def _getFormatOptions(self, options=None):
+ argv = []
+ if options and isinstance(options, list):
+ argv.extend(options)
+ argv.extend(self.defaultFormatOptions)
+ if self.label:
+ argv.extend(["-L", self.label])
+ argv.append(self.device)
+ return argv
+
+ @property
+ def resizeArgs(self):
+ argv = ["-r", "%dm" % (self.targetSize,), self.device]
+ return argv
+
+register_device_format(BTRFS)
+
+class XFS(FS):
+ """ XFS filesystem """
+ _type = "xfs"
+ _mkfs = "mkfs.xfs"
+ _modules = ["xfs"]
+ _labelfs = "xfs_admin"
+ _defaultFormatOptions = ["-f"]
+ _defaultLabelOptions = ["-L"]
+ _maxLabelChars = 16
+ _maxSize = 16 * 1024 * 1024
+ _formattable = True
+ _linuxNative = True
+ _supported = True
+ _dump = True
+ _check = True
+
+register_device_format(XFS)
+
+class NTFS(FS):
+ """ ntfs filesystem. """
+ _type = "ntfs"
+ _resizefs = "ntfsresize"
+ _fsck = "ntfsresize"
+ _resizable = True
+ _minSize = 1
+ _maxSize = 16 * 1024 * 1024
+ _defaultMountOptions = ["defaults"]
+ _defaultCheckOptions = ["-c"]
+
+ @property
+ def minSize(self):
+ """ The minimum filesystem size in megabytes. """
+ size = self._minSize
+ if self.exists and os.path.exists(self.device):
+ minSize = None
+ buf = util.execWithCapture(self.resizefsProg,
+ ["-m", self.device],
+ stderr = "/dev/tty5")
+ for l in buf.split("\n"):
+ if not l.startswith("Minsize"):
+ continue
+ try:
+ min = l.split(":")[1].strip()
+ minSize = int(min) + 250
+ except Exception, e:
+ minSize = None
+ self.installer.log.warning("Unable to parse output for minimum size on %s: %s" %(self.device, e))
+
+ if minSize is None:
+ self.installer.log.warning("Unable to discover minimum size of filesystem "
+ "on %s" %(self.device,))
+ else:
+ size = minSize
+
+ return size
+
+ @property
+ def resizeArgs(self):
+ # You must supply at least two '-f' options to ntfsresize or
+ # the proceed question will be presented to you.
+ argv = ["-ff", "-s", "%dM" % (self.targetSize,), self.device]
+ return argv
+
+register_device_format(NTFS)
+
+
+# if this isn't going to be mountable it might as well not be here
+class NFS(FS):
+ """ NFS filesystem. """
+ _type = "nfs"
+ _modules = ["nfs"]
+
+ def _deviceCheck(self, devspec):
+ if devspec is not None and ":" not in devspec:
+ raise ValueError("device must be of the form <host>:<path>")
+
+ @property
+ def mountable(self):
+ return False
+
+ def _setDevice(self, devspec):
+ self._deviceCheck(devspec)
+ self._device = devspec
+
+ def _getDevice(self):
+ return self._device
+
+ device = property(lambda f: f._getDevice(),
+ lambda f,d: f._setDevice(d),
+ doc="Full path the device this format occupies")
+
+register_device_format(NFS)
+
+
+class NFSv4(NFS):
+ """ NFSv4 filesystem. """
+ _type = "nfs4"
+ _modules = ["nfs4"]
+
+register_device_format(NFSv4)
+
+
+class Iso9660FS(FS):
+ """ ISO9660 filesystem. """
+ _type = "iso9660"
+ _formattable = False
+ _supported = True
+ _resizable = False
+ _bootable = False
+ _linuxNative = False
+ _dump = False
+ _check = False
+ _migratable = False
+ _defaultMountOptions = ["ro"]
+
+register_device_format(Iso9660FS)
+
+
+class NoDevFS(FS):
+ """ nodev filesystem base class """
+ _type = "nodev"
+
+ def __init__(self, *args, **kwargs):
+ FS.__init__(self, *args, **kwargs)
+ self.exists = True
+ self.device = self.type
+
+ def _setDevice(self, devspec):
+ self._device = devspec
+
+register_device_format(NoDevFS)
+
+
+class DevPtsFS(NoDevFS):
+ """ devpts filesystem. """
+ _type = "devpts"
+ _defaultMountOptions = ["gid=5", "mode=620"]
+
+register_device_format(DevPtsFS)
+
+
+# these don't really need to be here
+class ProcFS(NoDevFS):
+ _type = "proc"
+
+register_device_format(ProcFS)
+
+
+class SysFS(NoDevFS):
+ _type = "sysfs"
+
+register_device_format(SysFS)
+
+
+class TmpFS(NoDevFS):
+ _type = "tmpfs"
+
+register_device_format(TmpFS)
+
+
+class BindFS(FS):
+ _type = "bind"
+
+ @property
+ def mountable(self):
+ return True
+
+register_device_format(BindFS)
--- /dev/null
+#!/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)
--- /dev/null
+#!/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)
--- /dev/null
+#!/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)
--- /dev/null
+#!/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)
--- /dev/null
+#!/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
--- /dev/null
+#!/usr/bin/python
+
+import sys
+
+import storage