448 lines
15 KiB
Python
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()
|