qubes-installer-qubes-os/anaconda/pyanaconda/storage/formats/__init__.py
2013-01-24 01:45:53 +01:00

448 lines
15 KiB
Python

# __init__.py
# Entry point for anaconda storage formats subpackage.
#
# Copyright (C) 2009 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>
#
import os
from pyanaconda.baseudev import udev_get_device
from pyanaconda.iutil import notify_kernel
from pyanaconda.iutil import get_sysfs_path_by_name
from pyanaconda.iutil import execWithRedirect
from pyanaconda.anaconda_log import log_method_call
from ..errors import *
from ..devicelibs.dm import dm_node_from_name
from ..devicelibs.mdraid import md_node_from_name
from ..udev import udev_device_get_major, udev_device_get_minor
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
import logging
log = logging.getLogger("storage")
device_formats = {}
def register_device_format(fmt_class):
if not issubclass(fmt_class, DeviceFormat):
raise ValueError("arg1 must be a subclass of DeviceFormat")
device_formats[fmt_class._type] = fmt_class
log.debug("registered device format class %s as %s" % (fmt_class.__name__,
fmt_class._type))
default_fstypes = ("ext4", "ext3", "ext2")
def get_default_filesystem_type():
for fstype in default_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(default_fstypes))
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
log.debug("getFormat('%s') returning %s instance" % (fmt_type, className))
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]
# imputil is deprecated in python 2.6
try:
globals()[mod_name] = __import__(mod_name, globals(), locals(), [], -1)
except ImportError:
log.error("import of device format module '%s' failed" % mod_name)
from traceback import format_exc
log.debug(format_exc())
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
class DeviceFormat(object):
""" Generic device format. """
_type = None
_name = "Unknown"
_udevTypes = []
partedFlag = None
partedSystem = None
_formattable = False # can be formatted
_supported = False # is supported
_linuxNative = False # for clearpart
_packages = [] # required packages
_services = [] # required services
_resizable = False # can be resized
_migratable = False # can be migrated
_maxSize = 0 # maximum size in MB
_minSize = 0 # minimum size in MB
_dump = False
_check = False
_hidden = False # hide devices with this formatting?
def __init__(self, *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.device = kwargs.get("device")
self.uuid = kwargs.get("uuid")
self.exists = kwargs.get("exists")
self.options = kwargs.get("options")
self._majorminor = None
self._migrate = False
# don't worry about existence if this is a DeviceFormat instance
#if self.__class__ is DeviceFormat:
# self.exists = True
def __repr__(self):
s = ("%(classname)s instance (%(id)s) --\n"
" type = %(type)s name = %(name)s status = %(status)s\n"
" device = %(device)s uuid = %(uuid)s exists = %(exists)s\n"
" options = %(options)s supported = %(supported)s"
" formattable = %(format)s resizable = %(resize)s\n" %
{"classname": self.__class__.__name__, "id": "%#x" % id(self),
"type": self.type, "name": self.name, "status": self.status,
"device": self.device, "uuid": self.uuid, "exists": self.exists,
"options": self.options, "supported": self.supported,
"format": self.formattable, "resize": self.resizable})
return s
@property
def _existence_str(self):
exist = "existing"
if not self.exists:
exist = "non-existent"
return exist
@property
def desc(self):
return str(self.type)
def __str__(self):
return "%s %s" % (self._existence_str, self.desc)
@property
def dict(self):
d = {"type": self.type, "name": self.name, "device": self.device,
"uuid": self.uuid, "exists": self.exists,
"options": self.options, "supported": self.supported,
"resizable": self.resizable}
return d
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")
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):
log_method_call(self, device=self.device,
type=self.type, status=self.status)
def notifyKernel(self):
log_method_call(self, device=self.device,
type=self.type)
if not self.device:
return
if self.device.startswith("/dev/mapper/"):
try:
name = dm_node_from_name(os.path.basename(self.device))
except DMError:
log.warning("failed to get dm node for %s" % self.device)
return
elif self.device.startswith("/dev/md/"):
try:
name = md_node_from_name(os.path.basename(self.device))
except MDRaidError:
log.warning("failed to get md node for %s" % self.device)
return
else:
name = self.device
path = get_sysfs_path_by_name(name)
try:
notify_kernel(path, action="change")
except (ValueError, IOError) as e:
log.warning("failed to notify kernel of change: %s" % e)
def cacheMajorminor(self):
""" Cache the value of self.majorminor.
Once a device node of this format's device disappears (for instance
after a teardown), it is no longer possible to figure out the value
of self.majorminor pseudo-unique string. Call this method before
that happens for caching this.
"""
self._majorminor = None
try:
self.majorminor # this does the caching
except StorageError:
# entirely possible there's no majorminor, for instance an
# LVMVolumeGroup has got no device node and no sysfs path. In this
# case obviously, calling majorminor of this object later raises an
# exception.
pass
return self._majorminor
def create(self, *args, **kwargs):
log_method_call(self, device=self.device,
type=self.type, status=self.status)
# 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", self.device)
def destroy(self, *args, **kwargs):
log_method_call(self, device=self.device,
type=self.type, status=self.status)
try:
rc = execWithRedirect("wipefs", ["-a", self.device],
stderr="/dev/tty5",
stdout="/dev/tty5")
except Exception as e:
err = str(e)
else:
err = ""
if rc:
err = str(rc)
if err:
msg = "error wiping old signatures from %s: %s" % (self.device, err)
raise FormatDestroyError(msg)
self.exists = False
def setup(self, *args, **kwargs):
log_method_call(self, device=self.device,
type=self.type, status=self.status)
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):
log_method_call(self, device=self.device,
type=self.type, status=self.status)
@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 packages(self):
""" Packages required to manage formats of this type. """
return self._packages
@property
def services(self):
""" Services required to manage formats of this type. """
return self._services
@property
def resizable(self):
""" Can formats of this type be resized? """
return self._resizable and self.exists
@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
@property
def hidden(self):
""" Whether devices with this formatting should be hidden in UIs. """
return self._hidden
@property
def majorminor(self):
"""A string suitable for using as a pseudo-unique ID in kickstart."""
if not self._majorminor:
# If this is a device-mapper device, we have to get the DM node and
# build the sysfs path from that.
try:
device = dm_node_from_name(os.path.basename(self.device))
except DMError:
device = self.device
try:
sysfs_path = get_sysfs_path_by_name(device)
except RuntimeError:
raise StorageError("DeviceFormat.majorminor: "
"can not get majorminor for '%s'" % device)
dev = udev_get_device(sysfs_path[4:])
self._majorminor = "%03d%03d" %\
(udev_device_get_major(dev), udev_device_get_minor(dev))
return self._majorminor
collect_device_format_classes()