diff options
Diffstat (limited to 'pyanaconda/storage')
-rw-r--r-- | pyanaconda/storage/__init__.py | 27 | ||||
-rw-r--r-- | pyanaconda/storage/dasd.py | 2 | ||||
-rw-r--r-- | pyanaconda/storage/devicetree.py | 8 | ||||
-rw-r--r-- | pyanaconda/storage/fcoe.py | 2 | ||||
-rw-r--r-- | pyanaconda/storage/formats/biosboot.py | 4 | ||||
-rw-r--r-- | pyanaconda/storage/formats/fs.py | 8 | ||||
-rw-r--r-- | pyanaconda/storage/formats/prepboot.py | 4 | ||||
-rw-r--r-- | pyanaconda/storage/partitioning.py | 3 | ||||
-rw-r--r-- | pyanaconda/storage/platform.py | 409 | ||||
-rw-r--r-- | pyanaconda/storage/pyudev.py | 232 | ||||
-rw-r--r-- | pyanaconda/storage/tsort.py | 105 | ||||
-rw-r--r-- | pyanaconda/storage/udev.py | 69 |
12 files changed, 833 insertions, 40 deletions
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index f6cd38096..bc973e3cd 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -41,7 +41,6 @@ import nss.nss import parted from pykickstart.constants import * -from pyanaconda import tsort from storage_log import log_method_call from errors import * @@ -69,6 +68,7 @@ import dasd import util import arch from flags import flags +from platform import platform as _platform import shelve import contextlib @@ -278,17 +278,14 @@ class StorageDiscoveryConfig(object): self.zeroMbr = ksdata.zerombr.zerombr class Storage(object): - def __init__(self, data=None, platform=None): + def __init__(self, data=None): """ Create a Storage instance. Keyword Arguments: data - a pykickstart Handler instance - platform - a Platform instance - """ self.data = data - self.platform = platform self._bootloader = None self.config = StorageDiscoveryConfig() @@ -784,9 +781,6 @@ class Storage(object): - Needs some error handling """ - if not hasattr(self.platform, "diskLabelTypes"): - raise StorageError("can't clear partitions without platform data") - # Sort partitions by descending partition number to minimize confusing # things like multiple "destroy sda5" actions due to parted renumbering # partitions. This can still happen through the UI but it makes sense to @@ -842,10 +836,7 @@ class Storage(object): destroy_action = ActionDestroyFormat(disk) self.devicetree.registerAction(destroy_action) - if self.platform: - labelType = self.platform.bestDiskLabelType(disk) - else: - labelType = None + labelType = _platform.bestDiskLabelType(disk) # create a new disklabel on the disk newLabel = getFormat("disklabel", device=disk.path, @@ -1049,7 +1040,7 @@ class Storage(object): if fmt: mountpoint = getattr(fmt, "mountpoint", None) - kwargs["weight"] = self.platform.weight(mountpoint=mountpoint, + kwargs["weight"] = _platform.weight(mountpoint=mountpoint, fstype=fmt.type) @@ -1569,7 +1560,7 @@ class Storage(object): # # check that GPT boot disk on BIOS system has a BIOS boot partition # - if self.platform.weight(fstype="biosboot") and \ + if _platform.weight(fstype="biosboot") and \ stage1 and stage1.isDisk and \ getattr(stage1.format, "labelType", None) == "gpt": missing = True @@ -1645,8 +1636,7 @@ class Storage(object): @property def packages(self): pkgs = set() - if self.platform: - pkgs.update(self.platform.packages) + pkgs.update(_platform.packages) if self.bootloader: pkgs.update(self.bootloader.packages) @@ -1691,9 +1681,8 @@ class Storage(object): @property def bootloader(self): - if self._bootloader is None and self.platform is not None and \ - flags.installer_mode: - self._bootloader = get_bootloader(self.platform) + if self._bootloader is None and flags.installer_mode: + self._bootloader = get_bootloader() return self._bootloader diff --git a/pyanaconda/storage/dasd.py b/pyanaconda/storage/dasd.py index abf97b90f..bd3d4debc 100644 --- a/pyanaconda/storage/dasd.py +++ b/pyanaconda/storage/dasd.py @@ -26,7 +26,7 @@ from pyanaconda.storage.devices import deviceNameToDiskByPath from pyanaconda.storage import util from pyanaconda.storage import arch from . import ROOT_PATH -from pyanaconda.baseudev import udev_trigger +from .udev import udev_trigger import logging log = logging.getLogger("storage") diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index 0a5e7cdb5..f0d49f727 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -41,8 +41,8 @@ import devicelibs.loop import devicelibs.edd from udev import * import util -from pyanaconda import platform -from pyanaconda import tsort +from platform import platform +import tsort from flags import flags from storage_log import log_method_call, log_method_return import parted @@ -97,8 +97,6 @@ class DeviceTree(object): self.dasd = dasd self.mpathFriendlyNames = getattr(conf, "mpathFriendlyNames", True) - self.platform = platform.getPlatform() - self.diskImages = {} images = getattr(conf, "diskImages", {}) if images: @@ -1064,7 +1062,7 @@ class DeviceTree(object): # we're going to pass the "best" disklabel type into the DiskLabel # constructor, but it only has meaning for non-existent disklabels. - labelType = self.platform.bestDiskLabelType(device) + labelType = platform.bestDiskLabelType(device) try: format = getFormat("disklabel", diff --git a/pyanaconda/storage/fcoe.py b/pyanaconda/storage/fcoe.py index 22c6258b2..8e8e301e6 100644 --- a/pyanaconda/storage/fcoe.py +++ b/pyanaconda/storage/fcoe.py @@ -19,7 +19,7 @@ import os from . import util -from pyanaconda.baseudev import udev_settle +from .udev import udev_settle #from pyanaconda import isys from . import ROOT_PATH import logging diff --git a/pyanaconda/storage/formats/biosboot.py b/pyanaconda/storage/formats/biosboot.py index 861b59190..c3aef8d2b 100644 --- a/pyanaconda/storage/formats/biosboot.py +++ b/pyanaconda/storage/formats/biosboot.py @@ -23,6 +23,7 @@ from parted import PARTITION_BIOS_GRUB from ..errors import * +from .. import platform from . import DeviceFormat, register_device_format class BIOSBoot(DeviceFormat): @@ -53,8 +54,7 @@ class BIOSBoot(DeviceFormat): @property def supported(self): - from pyanaconda import platform - return isinstance(platform.getPlatform(), platform.X86) + return isinstance(platform.platform, platform.X86) register_device_format(BIOSBoot) diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py index 1d355c3be..b472e8727 100644 --- a/pyanaconda/storage/formats/fs.py +++ b/pyanaconda/storage/formats/fs.py @@ -35,6 +35,7 @@ import selinux from ..errors import * from . import DeviceFormat, register_device_format from .. import util +from .. import platform from ..flags import flags from parted import fileSystemType from ..storage_log import log_method_call @@ -975,9 +976,7 @@ class EFIFS(FATFS): @property def supported(self): - from pyanaconda import platform - p = platform.getPlatform() - return (isinstance(p, platform.EFI) and + return (isinstance(platform.platform, platform.EFI) and self.utilsAvailable) register_device_format(EFIFS) @@ -1210,8 +1209,7 @@ class AppleBootstrapFS(HFS): @property def supported(self): - from pyanaconda import platform - return (isinstance(platform.getPlatform(), platform.NewWorldPPC) + return (isinstance(platform.platform, platform.NewWorldPPC) and self.utilsAvailable) register_device_format(AppleBootstrapFS) diff --git a/pyanaconda/storage/formats/prepboot.py b/pyanaconda/storage/formats/prepboot.py index 884aa8fc4..dd2cf8c82 100644 --- a/pyanaconda/storage/formats/prepboot.py +++ b/pyanaconda/storage/formats/prepboot.py @@ -21,6 +21,7 @@ # from ..errors import * +from .. import platform from . import DeviceFormat, register_device_format from parted import PARTITION_PREP import os @@ -80,8 +81,7 @@ class PPCPRePBoot(DeviceFormat): @property def supported(self): - from pyanaconda import platform - return isinstance(platform.getPlatform(), platform.IPSeriesPPC) + return isinstance(platform.platform, platform.IPSeriesPPC) register_device_format(PPCPRePBoot) diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index 64aa250eb..243c62b80 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -721,9 +721,6 @@ def doPartitioning(storage): None """ - if not hasattr(storage.platform, "diskLabelTypes"): - raise StorageError(_("can't allocate partitions without platform data")) - disks = storage.partitioned if storage.config.exclusiveDisks: disks = [d for d in disks if d.name in storage.config.exclusiveDisks] diff --git a/pyanaconda/storage/platform.py b/pyanaconda/storage/platform.py new file mode 100644 index 000000000..5221a3065 --- /dev/null +++ b/pyanaconda/storage/platform.py @@ -0,0 +1,409 @@ +# +# platform.py: Architecture-specific information +# +# Copyright (C) 2009-2011 +# Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: Chris Lumens <clumens@redhat.com> +# +import os +import logging +log = logging.getLogger("storage") + +import parted + +from . import arch +from .flags import flags +from .partspec import PartSpec + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) +N_ = lambda x: x + +class Platform(object): + """Platform + + A class containing platform-specific information and methods for use + during installation. The intent is to eventually encapsulate all the + architecture quirks in one place to avoid lots of platform checks + throughout anaconda.""" + _minimumSector = 0 + _packages = [] + + # requirements for bootloader stage1 devices + _boot_stage1_device_types = [] + _boot_stage1_format_types = [] + _boot_stage1_mountpoints = [] + _boot_stage1_max_end_mb = None + _boot_stage1_raid_levels = [] + _boot_stage1_raid_metadata = [] + _boot_stage1_raid_member_types = [] + _boot_stage1_description = N_("bootloader device") + _boot_raid_description = N_("RAID Device") + _boot_partition_description = N_("First sector of boot partition") + _boot_descriptions = {} + + _disklabel_types = [] + _non_linux_format_types = [] + + def __init__(self): + """Creates a new Platform object. This is basically an abstract class. + You should instead use one of the platform-specific classes as + returned by getPlatform below. Not all subclasses need to provide + all the methods in this class.""" + + if flags.gpt and "gpt" in self._disklabel_types: + # move GPT to the top of the list + self._disklabel_types.remove("gpt") + self._disklabel_types.insert(0, "gpt") + + def __call__(self): + return self + + @property + def diskLabelTypes(self): + """A list of valid disklabel types for this architecture.""" + return self._disklabel_types + + @property + def defaultDiskLabelType(self): + """The default disklabel type for this architecture.""" + return self.diskLabelTypes[0] + + @property + def bootStage1ConstraintDict(self): + d = {"device_types": self._boot_stage1_device_types, + "format_types": self._boot_stage1_format_types, + "mountpoints": self._boot_stage1_mountpoints, + "max_end_mb": self._boot_stage1_max_end_mb, + "raid_levels": self._boot_stage1_raid_levels, + "raid_metadata": self._boot_stage1_raid_metadata, + "raid_member_types": self._boot_stage1_raid_member_types, + "descriptions": self._boot_descriptions} + return d + + def requiredDiskLabelType(self, device_type): + return None + + def bestDiskLabelType(self, device): + """The best disklabel type for the specified device.""" + if flags.testing: + return self.defaultDiskLabelType + + # if there's a required type for this device type, use that + labelType = self.requiredDiskLabelType(device.partedDevice.type) + log.debug("required disklabel type for %s (%s) is %s" + % (device.name, device.partedDevice.type, labelType)) + if not labelType: + # otherwise, use the first supported type for this platform + # that is large enough to address the whole device + labelType = self.defaultDiskLabelType + log.debug("default disklabel type for %s is %s" % (device.name, + labelType)) + for lt in self.diskLabelTypes: + l = parted.freshDisk(device=device.partedDevice, ty=lt) + if l.maxPartitionStartSector > device.partedDevice.length: + labelType = lt + log.debug("selecting %s disklabel for %s based on size" + % (labelType, device.name)) + break + + return labelType + + @property + def minimumSector(self, disk): + """Return the minimum starting sector for the provided disk.""" + return self._minimumSector + + @property + def packages (self): + _packages = self._packages + if flags.boot_cmdline.get('fips', None) == '1': + _packages.append('dracut-fips') + return _packages + + def setDefaultPartitioning(self): + """Return the default platform-specific partitioning information.""" + return [PartSpec(mountpoint="/boot", + size=500, weight=self.weight(mountpoint="/boot"))] + + def weight(self, fstype=None, mountpoint=None): + """ Given an fstype (as a string) or a mountpoint, return an integer + for the base sorting weight. This is used to modify the sort + algorithm for partition requests, mainly to make sure bootable + partitions and /boot are placed where they need to be.""" + if mountpoint == "/boot": + return 2000 + else: + return 0 + +class X86(Platform): + _boot_stage1_device_types = ["disk"] + _boot_mbr_description = N_("Master Boot Record") + _boot_descriptions = {"disk": _boot_mbr_description, + "partition": Platform._boot_partition_description, + "mdarray": Platform._boot_raid_description} + + + _disklabel_types = ["msdos", "gpt"] + # XXX hpfs, if reported by blkid/udev, will end up with a type of None + _non_linux_format_types = ["vfat", "ntfs", "hpfs"] + + def __init__(self): + super(X86, self).__init__() + + def setDefaultPartitioning(self): + """Return the default platform-specific partitioning information.""" + ret = Platform.setDefaultPartitioning(self) + ret.append(PartSpec(fstype="biosboot", size=1, + weight=self.weight(fstype="biosboot"))) + return ret + + def weight(self, fstype=None, mountpoint=None): + score = Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + if score: + return score + elif fstype == "biosboot": + return 5000 + else: + return 0 + +class EFI(Platform): + + _boot_stage1_format_types = ["efi"] + _boot_stage1_device_types = ["partition"] + _boot_stage1_mountpoints = ["/boot/efi"] + _boot_efi_description = N_("EFI System Partition") + _boot_descriptions = {"partition": _boot_efi_description, + "mdarray": Platform._boot_raid_description} + + _disklabel_types = ["gpt"] + # XXX hpfs, if reported by blkid/udev, will end up with a type of None + _non_linux_format_types = ["vfat", "ntfs", "hpfs"] + + def setDefaultPartitioning(self): + ret = Platform.setDefaultPartitioning(self) + ret.append(PartSpec(mountpoint="/boot/efi", fstype="efi", size=20, + maxSize=200, + grow=True, weight=self.weight(fstype="efi"))) + return ret + + def weight(self, fstype=None, mountpoint=None): + score = Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + if score: + return score + elif fstype == "efi" or mountpoint == "/boot/efi": + return 5000 + else: + return 0 + +class MacEFI(EFI): + _boot_stage1_format_types = ["hfs+"] + _boot_efi_description = N_("Apple EFI Boot Partition") + _non_linux_format_types = ["hfs+"] + _packages = ["mactel-boot"] + + def setDefaultPartitioning(self): + ret = Platform.setDefaultPartitioning(self) + ret.append(PartSpec(mountpoint="/boot/efi", fstype="hfs+", size=20, + maxSize=200, + grow=True, weight=self.weight(mountpoint="/boot/efi"))) + return ret + +class PPC(Platform): + _ppcMachine = arch.getPPCMachine() + _boot_stage1_device_types = ["partition"] + + @property + def ppcMachine(self): + return self._ppcMachine + +class IPSeriesPPC(PPC): + _boot_stage1_format_types = ["prepboot"] + _boot_stage1_max_end_mb = 10 + _boot_prep_description = N_("PReP Boot Partition") + _boot_descriptions = {"partition": _boot_prep_description} + _disklabel_types = ["msdos"] + + def setDefaultPartitioning(self): + ret = PPC.setDefaultPartitioning(self) + ret.append(PartSpec(fstype="prepboot", size=4, + weight=self.weight(fstype="prepboot"))) + return ret + + def weight(self, fstype=None, mountpoint=None): + score = Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + if score: + return score + elif fstype == "prepboot": + return 5000 + else: + return 0 + +class NewWorldPPC(PPC): + _boot_stage1_format_types = ["appleboot"] + _boot_apple_description = N_("Apple Bootstrap Partition") + _boot_descriptions = {"partition": _boot_apple_description} + _disklabel_types = ["mac"] + _non_linux_format_types = ["hfs", "hfs+"] + + def setDefaultPartitioning(self): + ret = Platform.setDefaultPartitioning(self) + ret.append(PartSpec(fstype="appleboot", size=1, maxSize=1, + weight=self.weight(fstype="appleboot"))) + return ret + + def weight(self, fstype=None, mountpoint=None): + score = Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + if score: + return score + elif fstype == "appleboot": + return 5000 + else: + return 0 + +class PS3(PPC): + pass + +class S390(Platform): + _packages = ["s390utils"] + _disklabel_types = ["msdos", "dasd"] + _boot_stage1_device_types = ["disk", "partition"] + _boot_dasd_description = N_("DASD") + _boot_zfcp_description = N_("zFCP") + _boot_descriptions = {"dasd": _boot_dasd_description, + "zfcp": _boot_zfcp_description, + "partition": Platform._boot_partition_description} + + def __init__(self): + Platform.__init__(self) + + def setDefaultPartitioning(self): + """Return the default platform-specific partitioning information.""" + return [PartSpec(mountpoint="/boot", size=500, + weight=self.weight(mountpoint="/boot"), lv=True, + singlePV=True)] + + def requiredDiskLabelType(self, device_type): + """The required disklabel type for the specified device type.""" + if device_type == parted.DEVICE_DASD: + return "dasd" + + return super(S390, self).requiredDiskLabelType(device_type) + +class Sparc(Platform): + _boot_stage1_format_types = [] + _boot_stage1_mountpoints = [] + _boot_stage1_max_end_mb = None + _disklabel_types = ["sun"] + + @property + def minimumSector(self, disk): + (cylinders, heads, sectors) = disk.device.biosGeometry + start = long(sectors * heads) + start /= long(1024 / disk.device.sectorSize) + return start+1 + +class ARM(Platform): + _armMachine = None + _boot_stage1_device_types = ["disk"] + _boot_mbr_description = N_("Master Boot Record") + _boot_descriptions = {"disk": _boot_mbr_description, + "partition": Platform._boot_partition_description} + + _disklabel_types = ["msdos"] + + @property + def armMachine(self): + if not self._armMachine: + self._armMachine = arch.getARMMachine() + return self._armMachine + + def weight(self, fstype=None, mountpoint=None): + """Return the ARM platform-specific weight for the / partition. + On ARM images '/' must be the last partition, so we try to + weight it accordingly.""" + if mountpoint == "/": + return -100 + else: + return Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + +class omapARM(ARM): + _boot_stage1_format_types = ["vfat"] + _boot_stage1_device_types = ["partition"] + _boot_stage1_mountpoints = ["/boot/uboot"] + _boot_uboot_description = N_("U-Boot Partition") + _boot_descriptions = {"partition": _boot_uboot_description} + + def setDefaultPartitioning(self): + """Return the ARM-OMAP platform-specific partitioning information.""" + ret = [PartSpec(mountpoint="/boot/uboot", fstype="vfat", + size=20, maxSize=200, grow=True, + weight=self.weight(fstype="vfat", mountpoint="/boot/uboot"))] + ret.append(PartSpec(mountpoint="/", fstype="ext4", + size=2000, maxSize=3000, + weight=self.weight(mountpoint="/"))) + return ret + + def weight(self, fstype=None, mountpoint=None): + """Return the ARM-OMAP platform-specific weights for the uboot + and / partitions. On OMAP, uboot must be the first partition, + and '/' must be the last partition, so we try to weight them + accordingly.""" + if fstype == "vfat" and mountpoint == "/boot/uboot": + return 6000 + elif mountpoint == "/": + return -100 + else: + return Platform.weight(self, fstype=fstype, mountpoint=mountpoint) + +def getPlatform(): + """Check the architecture of the system and return an instance of a + Platform subclass to match. If the architecture could not be determined, + raise an exception.""" + if arch.isPPC(): + ppcMachine = arch.getPPCMachine() + + if (ppcMachine == "PMac" and arch.getPPCMacGen() == "NewWorld"): + return NewWorldPPC() + elif ppcMachine in ["iSeries", "pSeries"]: + return IPSeriesPPC() + elif ppcMachine == "PS3": + return PS3() + else: + raise SystemError, "Unsupported PPC machine type: %s" % ppcMachine + elif arch.isS390(): + return S390() + elif arch.isSparc(): + return Sparc() + elif arch.isEfi(): + if arch.isMactel(): + return MacEFI() + else: + return EFI() + elif arch.isX86(): + return X86() + elif arch.isARM(): + armMachine = arch.getARMMachine() + if armMachine == "omap": + return omapARM() + else: + return ARM() + else: + raise SystemError, "Could not determine system architecture." + +global platform +platform = getPlatform() diff --git a/pyanaconda/storage/pyudev.py b/pyanaconda/storage/pyudev.py new file mode 100644 index 000000000..705b93d80 --- /dev/null +++ b/pyanaconda/storage/pyudev.py @@ -0,0 +1,232 @@ +from __future__ import print_function + +import sys +import os +import fnmatch +from ctypes import * + + +# XXX this one may need some tweaking... +def find_library(name, somajor=0): + env = os.environ.get("LD_LIBRARY_PATH") + common = ["/lib64", "/lib"] + + if env: + libdirs = env.split(":") + common + else: + libdirs = common + + libdirs = filter(os.path.isdir, libdirs) + + for dir in libdirs: + files = fnmatch.filter(os.listdir(dir), "lib%s.so.%d" % (name, somajor)) + files = [os.path.join(dir, file) for file in files] + + if files: + break + + if files: + return files[0] + else: + return None + +# find the udev library +name = "udev" +somajor = 1 +libudev = find_library(name=name, somajor=somajor) + +if not libudev or not os.path.exists(libudev): + raise ImportError, "No library named %s.%d" % (name, somajor) + +# load the udev library +libudev = CDLL(libudev) + + +# create aliases for needed functions and set the return types where needed +libudev_udev_new = libudev.udev_new +libudev_udev_new.argtypes = [] +libudev_udev_new.restype = c_void_p +libudev_udev_unref = libudev.udev_unref +libudev_udev_unref.argtypes = [ c_void_p ] + +libudev_udev_device_new_from_syspath = libudev.udev_device_new_from_syspath +libudev_udev_device_new_from_syspath.restype = c_void_p +libudev_udev_device_new_from_syspath.argtypes = [ c_void_p, c_char_p ] +libudev_udev_device_unref = libudev.udev_device_unref +libudev_udev_device_unref.argtypes = [ c_void_p ] + +libudev_udev_device_get_syspath = libudev.udev_device_get_syspath +libudev_udev_device_get_syspath.restype = c_char_p +libudev_udev_device_get_syspath.argtypes = [ c_void_p ] +libudev_udev_device_get_sysname = libudev.udev_device_get_sysname +libudev_udev_device_get_sysname.restype = c_char_p +libudev_udev_device_get_sysname.argtypes = [ c_void_p ] +libudev_udev_device_get_devpath = libudev.udev_device_get_devpath +libudev_udev_device_get_devpath.restype = c_char_p +libudev_udev_device_get_devpath.argtypes = [ c_void_p ] +libudev_udev_device_get_devtype = libudev.udev_device_get_devtype +libudev_udev_device_get_devtype.restype = c_char_p +libudev_udev_device_get_devtype.argtypes = [ c_void_p ] +libudev_udev_device_get_devnode = libudev.udev_device_get_devnode +libudev_udev_device_get_devnode.restype = c_char_p +libudev_udev_device_get_devnode.argtypes = [ c_void_p ] +libudev_udev_device_get_subsystem = libudev.udev_device_get_subsystem +libudev_udev_device_get_subsystem.restype = c_char_p +libudev_udev_device_get_subsystem.argtypes = [ c_void_p ] +libudev_udev_device_get_sysnum = libudev.udev_device_get_sysnum +libudev_udev_device_get_sysnum.restype = c_char_p +libudev_udev_device_get_sysnum.argtypes = [ c_void_p ] + +libudev_udev_device_get_properties_list_entry = libudev.udev_device_get_properties_list_entry +libudev_udev_device_get_properties_list_entry.restype = c_void_p +libudev_udev_device_get_properties_list_entry.argtypes = [ c_void_p ] +libudev_udev_list_entry_get_next = libudev.udev_list_entry_get_next +libudev_udev_list_entry_get_next.restype = c_void_p +libudev_udev_list_entry_get_next.argtypes = [ c_void_p ] + +libudev_udev_list_entry_get_name = libudev.udev_list_entry_get_name +libudev_udev_list_entry_get_name.restype = c_char_p +libudev_udev_list_entry_get_name.argtypes = [ c_void_p ] +libudev_udev_list_entry_get_value = libudev.udev_list_entry_get_value +libudev_udev_list_entry_get_value.restype = c_char_p +libudev_udev_list_entry_get_value.argtypes = [ c_void_p ] + +libudev_udev_enumerate_new = libudev.udev_enumerate_new +libudev_udev_enumerate_new.restype = c_void_p +libudev_udev_enumerate_new.argtypes = [ c_void_p ] +libudev_udev_enumerate_unref = libudev.udev_enumerate_unref +libudev_udev_enumerate_unref.argtypes = [ c_void_p ] + +libudev_udev_enumerate_add_match_subsystem = libudev.udev_enumerate_add_match_subsystem +libudev_udev_enumerate_add_match_subsystem.restype = c_int +libudev_udev_enumerate_add_match_subsystem.argtypes = [ c_void_p, c_char_p ] +libudev_udev_enumerate_scan_devices = libudev.udev_enumerate_scan_devices +libudev_udev_enumerate_scan_devices.restype = c_int +libudev_udev_enumerate_scan_devices.argtypes = [ c_void_p ] +libudev_udev_enumerate_get_list_entry = libudev.udev_enumerate_get_list_entry +libudev_udev_enumerate_get_list_entry.restype = c_void_p +libudev_udev_enumerate_get_list_entry.argtypes = [ c_void_p ] + +libudev_udev_device_get_devlinks_list_entry = libudev.udev_device_get_devlinks_list_entry +libudev_udev_device_get_devlinks_list_entry.restype = c_void_p +libudev_udev_device_get_devlinks_list_entry.argtypes = [ c_void_p ] + + +class UdevDevice(dict): + + def __init__(self, udev, sysfs_path): + dict.__init__(self) + + # create new udev device from syspath + udev_device = libudev_udev_device_new_from_syspath(udev, sysfs_path) + if not udev_device: + # device does not exist + return + + # set syspath and sysname properties + self.syspath = libudev_udev_device_get_syspath(udev_device) + self.sysname = libudev_udev_device_get_sysname(udev_device) + + # get the devlinks list + devlinks = [] + devlinks_entry = libudev_udev_device_get_devlinks_list_entry(udev_device) + + while devlinks_entry: + path = libudev_udev_list_entry_get_name(devlinks_entry) + devlinks.append(path) + + devlinks_entry = libudev_udev_list_entry_get_next(devlinks_entry) + + # add devlinks list to the dictionary + self["symlinks"] = devlinks + + # get the first property entry + property_entry = libudev_udev_device_get_properties_list_entry(udev_device) + + while property_entry: + name = libudev_udev_list_entry_get_name(property_entry) + value = libudev_udev_list_entry_get_value(property_entry) + + # lvm outputs values for multiple lvs in one line + # we want to split them and make a list + # if the first lv's value is empty we end up with a value starting + # with name=, prepend a space that our split does the right thing + if value.startswith("%s=" % name): + value = " " + value + + if value.count(" %s=" % name): + value = value.split(" %s=" % name) + + self[name] = value + + # get next property entry + property_entry = libudev_udev_list_entry_get_next(property_entry) + + # set additional properties + self.devpath = libudev_udev_device_get_devpath(udev_device) + self.subsystem = libudev_udev_device_get_subsystem(udev_device) + self.devtype = libudev_udev_device_get_devtype(udev_device) + self.sysnum = libudev_udev_device_get_sysnum(udev_device) + self.devnode = libudev_udev_device_get_devnode(udev_device) + + # cleanup + libudev_udev_device_unref(udev_device) + + +class Udev(object): + + def __init__(self): + self.udev = libudev_udev_new() + + def create_device(self, sysfs_path): + return UdevDevice(self.udev, sysfs_path) + + def enumerate_devices(self, subsystem=None): + enumerate = libudev_udev_enumerate_new(self.udev) + + # add the match subsystem + if subsystem is not None: + rc = libudev_udev_enumerate_add_match_subsystem(enumerate, subsystem) + if not rc == 0: + print("error: unable to add the match subsystem", file=sys.stderr) + libudev_udev_enumerate_unref(enumerate) + return [] + + # scan the devices + rc = libudev_udev_enumerate_scan_devices(enumerate) + if not rc == 0: + print("error: unable to enumerate the devices", file=sys.stderr) + libudev_udev_enumerate_unref(enumerate) + return [] + + # create the list of sysfs paths + sysfs_paths = [] + + # get the first list entry + list_entry = libudev_udev_enumerate_get_list_entry(enumerate) + + while list_entry: + sysfs_path = libudev_udev_list_entry_get_name(list_entry) + sysfs_paths.append(sysfs_path) + + # get next list entry + list_entry = libudev_udev_list_entry_get_next(list_entry) + + # cleanup + libudev_udev_enumerate_unref(enumerate) + + return sysfs_paths + + def scan_devices(self, sysfs_paths=None): + if sysfs_paths is None: + sysfs_paths = self.enumerate_devices() + + for sysfs_path in sysfs_paths: + device = self.create_device(sysfs_path) + + if device: + yield device + + def unref(self): + libudev_udev_unref(self.udev) + self.udev = None diff --git a/pyanaconda/storage/tsort.py b/pyanaconda/storage/tsort.py new file mode 100644 index 000000000..52a871f56 --- /dev/null +++ b/pyanaconda/storage/tsort.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# tsort.py +# Topological sorting. +# +# Copyright (C) 2010 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# + +class CyclicGraphError(Exception): + pass + +def tsort(graph): + order = [] # sorted list of items + + if not graph or not graph['items']: + return order + + # determine which nodes have no incoming edges + roots = [n for n in graph['items'] if graph['incoming'][n] == 0] + if not roots: + raise CyclicGraphError("no root nodes") + + visited = [] # list of nodes visited, for cycle detection + while roots: + # remove a root, add it to the order + root = roots.pop() + if root in visited: + raise CyclicGraphError("graph contains cycles") + + visited.append(root) + i = graph['items'].index(root) + order.append(root) + # remove each edge from the root to another node + for (parent, child) in [e for e in graph['edges'] if e[0] == root]: + graph['incoming'][child] -= 1 + graph['edges'].remove((parent, child)) + # if destination node is now a root, add it to roots + if graph['incoming'][child] == 0: + roots.append(child) + + if len(graph['items']) != len(visited): + raise CyclicGraphError("graph contains cycles") + + + return order + +def create_graph(items, edges): + """ Create a graph based on a list of items and a list of edges. + + Arguments: + + items - an iterable containing (hashable) items to sort + edges - an iterable containing (parent, child) edge pair tuples + + Return Value: + + The return value is a dictionary representing the directed graph. + It has three keys: + + items is the same as the input argument of the same name + edges is the same as the input argument of the same name + incoming is a dict of incoming edge count hashed by item + + """ + graph = {'items': [], # the items to sort + 'edges': [], # partial order info: (parent, child) pairs + 'incoming': {}} # incoming edge count for each item + + graph['items'] = items + graph['edges'] = edges + for item in items: + graph['incoming'][item] = 0 + + for (parent, child) in edges: + graph['incoming'][child] += 1 + + return graph + + +if __name__ == "__main__": + + items = [5, 2, 3, 4, 1] + edges = [(1, 2), (2, 4), (4, 5), (3, 2)] + + print items + print edges + + graph = create_graph(items, edges) + print tsort(graph) + diff --git a/pyanaconda/storage/udev.py b/pyanaconda/storage/udev.py index cb9601b1e..f8ba09c9b 100644 --- a/pyanaconda/storage/udev.py +++ b/pyanaconda/storage/udev.py @@ -1,7 +1,7 @@ # udev.py # Python module for querying the udev database for device information. # -# Copyright (C) 2009 Red Hat, Inc. +# Copyright (C) 2009, 2013 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of @@ -18,6 +18,7 @@ # Red Hat, Inc. # # Red Hat Author(s): Dave Lehman <dlehman@redhat.com> +# Chris Lumens <clumens@redhat.com> # import os @@ -25,11 +26,75 @@ import re import util from errors import * -from pyanaconda.baseudev import * + +import pyudev +global_udev = pyudev.Udev() import logging log = logging.getLogger("storage") +def udev_enumerate_devices(deviceClass="block"): + devices = global_udev.enumerate_devices(subsystem=deviceClass) + return [path[4:] for path in devices] + +def udev_get_device(sysfs_path): + if not os.path.exists("/sys%s" % sysfs_path): + log.debug("%s does not exist" % sysfs_path) + return None + + # XXX we remove the /sys part when enumerating devices, + # so we have to prepend it when creating the device + dev = global_udev.create_device("/sys" + sysfs_path) + + if dev: + dev["name"] = dev.sysname + dev["sysfs_path"] = sysfs_path + + # now add in the contents of the uevent file since they're handy + dev = udev_parse_uevent_file(dev) + + return dev + +def udev_get_devices(deviceClass="block"): + udev_settle() + entries = [] + for path in udev_enumerate_devices(deviceClass): + entry = udev_get_device(path) + if entry: + entries.append(entry) + return entries + +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_settle(): + # wait maximal 300 seconds for udev to be done running blkid, lvm, + # mdadm etc. This large timeout is needed when running on machines with + # lots of disks, or with slow disks + util.run_program(["udevadm", "settle", "--timeout=300"]) + +def udev_trigger(subsystem=None, action="add", name=None): + argv = ["trigger", "--action=%s" % action] + if subsystem: + argv.append("--subsystem-match=%s" % subsystem) + if name: + argv.append("--sysname-match=%s" % name) + + util.run_program(["udevadm"] + argv) + udev_settle() + def udev_resolve_devspec(devspec): if not devspec: return None |