adapt partition plugin to the new blockdevice implementation

reduce minimum storage block size from 20 to 10 (the config partition is small)
This commit is contained in:
lars 2007-09-11 10:22:48 +00:00
parent 4a4778d876
commit 9c649312d0
5 changed files with 143 additions and 301 deletions

View file

@ -27,7 +27,7 @@ import subprocess
import os
import re
import logging
import cryptobox.core.tools as cbox_tools
import cryptobox.core.blockdevice as blockdevice_tools
import cryptobox.plugins.base
from cryptobox.core.exceptions import *
@ -58,15 +58,19 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
self.__prepare_dataset()
## retrieve some values from 'args' - defaults are empty
self.blockdevice = self.__get_selected_device(args)
self.with_config_partition = self.__is_with_config_partition()
if self.blockdevice:
self.with_config_partition = self.__is_with_config_partition()
self.blockdevice_size = self.__get_available_device_size(self.blockdevice)
else:
self.with_config_partition = False
self.blockdevice_size = 0
self.cbox.log.debug(
"partition plugin: selected device=%s" % str(self.blockdevice))
self.blockdevice_size = self.__get_available_device_size(self.blockdevice)
## no (or invalid) device was supplied
if not self.blockdevice:
return self.__action_select_device()
## exit if the blockdevice is not writeable
if not os.access(self.blockdevice, os.W_OK):
if not os.access(self.blockdevice.devnodes[0], os.W_OK):
self.hdf["Data.Warning"] = "DeviceNotWriteable"
return self.__action_select_device()
## no confirm setting?
@ -91,15 +95,19 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
"partition: invalid partition number to delete (%s)" % del_args[0])
return self.__action_select_device()
return self.__action_del_partition(args, num_part)
else: # for "select_device" and for invalid targets
else:
## for "select_device" and for invalid targets
return self.__action_select_device()
def get_status(self):
"""The status of this plugin is the selected device and some information.
"""
return "%s / %s / %s" % (self.blockdevice, self.blockdevice_size,
self.with_config_partition)
if not self.blockdevice:
return "no blockdevice selected"
else:
return "%s / %s / %s" % (self.blockdevice.name,
self.blockdevice_size, self.with_config_partition)
def get_warnings(self):
@ -131,7 +139,13 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
"""Check the selected device (valid, not busy, ...).
"""
try:
blockdevice = args["block_device"]
blockdevice_name = args["block_device"]
found = [ dev for dev in
blockdevice_tools.Blockdevices().get_partitionable_devices()
if dev.name == blockdevice_name ]
if not found:
return None
blockdevice = found[0]
except KeyError:
return None
if not self.__is_device_valid(blockdevice):
@ -149,8 +163,6 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
return False
if not self.cbox.is_device_allowed(blockdevice):
return False
if not blockdevice in cbox_tools.get_parent_blockdevices():
return False
return True
@ -158,9 +170,10 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
"""check if the device (or one of its partitions) is mounted
"""
## the config partition is ignored, as it will get unmounted if necessary
for cont in self.cbox.get_container_list():
if cbox_tools.is_part_of_blockdevice(blockdevice, cont.get_device()) \
and cont.is_mounted():
for dev in blockdevice.children:
container = self.cbox.get_container(
blockdevice_tools.get_blockdevice(dev).devnodes[0])
if container and (container.is_mounted() or container.is_busy()):
return True
return False
@ -168,15 +181,17 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
def __action_select_device(self):
"""Show a form to select the device for partitioning.
"""
block_devices = [e
for e in cbox_tools.get_parent_blockdevices()
if self.cbox.is_device_allowed(e)]
block_devices = [ e for e in
blockdevice_tools.Blockdevices().get_partitionable_devices()
if self.cbox.is_device_allowed(e) ]
counter = 0
for dev in block_devices:
self.hdf[self.hdf_prefix + "BlockDevices.%d.name" % counter] = dev
self.hdf[self.hdf_prefix + "BlockDevices.%d.name" % counter] = \
dev.name
self.hdf[self.hdf_prefix + "BlockDevices.%d.size" % counter] = \
cbox_tools.get_blockdevice_size_humanly(dev)
self.cbox.log.debug("found a suitable block device: %s" % dev)
dev.size_human
self.cbox.log.debug("found a suitable block device: %s" % \
dev.devnodes[0])
counter += 1
if self.with_config_partition:
self.hdf[self.hdf_prefix + "CreateConfigPartition"] = "1"
@ -189,7 +204,7 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
def __action_add_partition(self, args):
"""Add a selected partition to the currently proposed partition table.
"""
self.hdf[self.hdf_prefix + "Device"] = self.blockdevice
self.hdf[self.hdf_prefix + "Device"] = self.blockdevice.name
self.hdf[self.hdf_prefix + "Device.Size"] = self.blockdevice_size
parts = self.__get_partitions_from_args(args)
self.__set_partition_data(parts)
@ -199,7 +214,7 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
def __action_del_partition(self, args, part_num):
"""Remove a partition from the proposed partition table.
"""
self.hdf[self.hdf_prefix + "Device"] = self.blockdevice
self.hdf[self.hdf_prefix + "Device"] = self.blockdevice.name
self.hdf[self.hdf_prefix + "Device.Size"] = self.blockdevice_size
parts = self.__get_partitions_from_args(args)
## valid partition number to be deleted?
@ -215,8 +230,11 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
parts = self.__get_partitions_from_args(args)
if parts:
self.__set_partition_data(parts)
if cbox_tools.is_part_of_blockdevice(self.blockdevice,
self.cbox.prefs.get_active_partition()):
## umount config partition if necessary
config_partition = self.cbox.prefs.get_active_partition()
if [ dev for dev in self.blockdevice.children
if config_partition in
blockdevice_tools.get_blockdevice(dev).devnodes ]:
self.cbox.prefs.umount_partition()
if not self.__run_fdisk(parts):
self.hdf["Data.Warning"] = "Plugins.partition.PartitioningFailed"
@ -257,9 +275,10 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
format_ok = False
if format_ok:
self.hdf["Data.Success"] = "Plugins.partition.Partitioned"
return { "plugin":"system_preferences", "values":[] }
else:
self.hdf["Data.Warning"] = "Plugins.partition.FormattingFailed"
return "empty"
return "empty"
else:
return self.__action_add_partition(args)
@ -270,15 +289,17 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
import types
## we do not have to take special care for a possible config partition
parts = [ { "size": self.blockdevice_size, "type": "windows" } ]
## umount partition if necessary
if cbox_tools.is_part_of_blockdevice(self.blockdevice,
self.cbox.prefs.get_active_partition()):
## umount config partition if necessary
config_partition = self.cbox.prefs.get_active_partition()
if [ dev for dev in self.blockdevice.children
if config_partition in
blockdevice_tools.get_blockdevice(dev).devnodes ]:
self.cbox.prefs.umount_partition()
## partition it
if not self.__run_fdisk(parts):
self.hdf["Data.Warning"] = "Plugins.partition.PartitioningFailed"
return None
## "formatPartitions" is a generator, returning device names and bolean values
## "formatPartitions" is a generator, returning device names and boolean values
result = [e for e in self.__format_partitions(parts)
if type(e) == types.BooleanType]
if self.with_config_partition:
@ -317,11 +338,12 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
for ptype in PARTTYPES.keys():
self.hdf[self.hdf_prefix + "Types.%s" % ptype] = ptype
## store the currently existing partitions of the choosen block device
current_containers = [ e for e in self.cbox.get_container_list()
if cbox_tools.is_part_of_blockdevice(self.blockdevice, e.get_device()) ]
current_containers = [ blockdevice_tools.get_blockdevice(dev)
for dev in self.blockdevice.children
if blockdevice_tools.get_blockdevice(dev).is_storage() ]
for (index, cont) in enumerate(current_containers):
self.hdf[self.hdf_prefix + "ExistingContainers.%d" % index] = \
cont.get_device()
cont.name
def __get_partitions_from_args(self, args):
@ -361,11 +383,11 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
"""calculate the available size (MB) of the device
also consider a (possible) configuration partition
"""
device_size = cbox_tools.get_blockdevice_size(device)
if device_size < 0:
return 0
device_size = device.size
if self.with_config_partition:
device_size -= CONFIGPARTITION["size"]
if device_size < 0:
return 0
return device_size
@ -378,7 +400,12 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
if not active:
return True
## check if the active one is part of the current device
return cbox_tools.is_part_of_blockdevice(self.blockdevice, active)
if [ dev for dev in self.blockdevice.children
if active in
blockdevice_tools.get_blockdevice(dev).devnodes ]:
return True
else:
return False
return False
@ -402,7 +429,7 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
"plugin",
os.path.join(self.plugin_dir, "root_action.py"),
"partition",
self.blockdevice])
self.blockdevice.devnodes[0]])
for line in self.__get_sfdisk_layout(parts, is_filled):
proc.stdin.write(line + "\n")
#TODO: if running inside of an uml, then sfdisk hangs at "nanosleep({3,0})"
@ -455,22 +482,22 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
part_num = 1
## maybe a config partition?
if self.with_config_partition:
dev_name = self.__get_partition_name(self.blockdevice, part_num)
dev_name = self.__get_partition_device(self.blockdevice, part_num)
self.cbox.log.info("formatting config partition (%s)" % dev_name)
if self.__format_one_partition(dev_name, CONFIGPARTITION["fs"]):
self.__set_label_of_partition(dev_name,
self.cbox.prefs["Main"]["ConfigVolumeLabel"])
part_num += 1
## the first data partition
dev_name = self.__get_partition_name(self.blockdevice, part_num)
dev_name = self.__get_partition_device(self.blockdevice, part_num)
part_type = PARTTYPES[parts[0]["type"]][1]
self.cbox.log.info("formatting partition (%s) as '%s'" % (dev_name, part_type))
yield self.__format_one_partition(dev_name, part_type)
del parts[0]
## other data partitions
part_num = 5
part_num += 1
while parts:
dev_name = self.__get_partition_name(self.blockdevice, part_num)
dev_name = self.__get_partition_device(self.blockdevice, part_num)
part_type = PARTTYPES[parts[0]["type"]][1]
self.cbox.log.info("formatting partition (%s) as '%s'" % \
(dev_name, part_type))
@ -480,17 +507,26 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
return
def __get_partition_name(self, blockdev, number):
def __get_partition_device(self, blockdev, number):
"""Return the devicename of a specific partition of a device
No tests are performed, whether the partition exists or
not.
"""
if re.search("[0-9]$", blockdev):
## blockdev ends with a digit, so it is a partition, we insert a 'p'
return "%sp%d" % (blockdev, number)
else:
## whole disk, no 'p' necessary
return "%s%d" % (blockdev, number)
valid_children = []
## filter the storage devices
for child in blockdev.children:
childdev = blockdevice_tools.get_blockdevice(child)
if childdev and childdev.is_storage():
valid_children.append(childdev)
sorted = blockdevice_tools.get_sorted_devices(valid_children)
if number <= len(sorted):
childdev = sorted[number-1]
if childdev:
return childdev.devnodes[0]
self.cbox.log.warn("Failed to get the partition name (%s, %d)" % \
(blockdev, number))
## return some guessed value - we should never get here ...
return "%s%d" % (blockdev.devnodes[0], number)
def __format_one_partition(self, dev_name, fs_type):
@ -502,7 +538,8 @@ class partition(cryptobox.plugins.base.CryptoBoxPlugin):
if e.get_device() == dev_name]
## call "mkfs"
try:
cont = cryptobox.core.container.CryptoBoxContainer(dev_name, self.cbox)
format_dev = blockdevice_tools.get_blockdevice(dev_name)
cont = cryptobox.core.container.CryptoBoxContainer(format_dev, self.cbox)
cont.create(cryptobox.core.container.CONTAINERTYPES["plain"], fs_type=fs_type)
except (CBInvalidType, CBCreateError, CBVolumeIsActive), err_msg:
self.cbox.log.warn(err_msg)

View file

@ -38,7 +38,7 @@ LOGGER = logging.getLogger("CryptoBox")
DEFAULT_SYSBLOCK_DIR = '/sys/block'
DEFAULT_DEVNODE_DIR = '/dev'
MINIMUM_STORAGE_SIZE = 20
MINIMUM_STORAGE_SIZE = 10
MAJOR_DEVNUM_RAM = 1
MAJOR_DEVNUM_LOOP = 7
MAJOR_DEVNUM_MD_RAID = 9
@ -98,6 +98,13 @@ class Blockdevice:
sysblock_dir=DEFAULT_SYSBLOCK_DIR,
devnode_dir=DEFAULT_DEVNODE_DIR):
"""initialize the blockdevice
@type dev: string
@param dev: The /sys/block/ subdirectory describing a blockdevice
@type sysblock_dir: string
@param sysblock_dir: The linux /sys/ directory. Default is '/sys'.
@type devnode_dir: string
@param devnode_dir: The linux /dev/ directory. Default is '/dev'.
"""
self.devdir = dev
self.devnode_dir = devnode_dir
@ -122,6 +129,9 @@ class Blockdevice:
usually we will have to reset the cache, too
just in case of first-time initialization, this is not necessary
@type empty_cache: boolean
@param empty_cache: Whether to discard the cached information or not.
"""
CACHE.reset(["blockdevice_info", self.name])
self.devnum = self.__get_major_minor()
@ -141,6 +151,9 @@ class Blockdevice:
"""check if the device is usable and valid
causes of invalidity: ram device, loop device, removable device
@rtype: boolean
@return: 'True' for a valid blockdevice
"""
if not self.devnodes:
return False
@ -165,6 +178,9 @@ class Blockdevice:
def is_storage(self):
"""return if this device can be used as a storage
@rtype: boolean
@return: 'True' for a device usable as a storage
"""
## check the cache first
cache_link = ["blockdevice_info", self.name, "is_storage"]
@ -755,11 +771,24 @@ def get_blockdevice(dev,
sysblock_dir=DEFAULT_SYSBLOCK_DIR,
devnode_dir=DEFAULT_DEVNODE_DIR):
if os.path.isabs(dev):
if os.path.isfile(os.path.join(dev, "dev")):
devdir = dev
## it is an absolute path
if dev.startswith(devnode_dir):
## it is the name of a devicenode (e.g.: '/dev/hda1')
found_dev = [ a_dev for a_dev in Blockdevices(
sysblock_dir, devnode_dir).get_devices()
if dev in a_dev.devnodes ]
if found_dev:
devdir = found_dev[0].devdir
else:
return None
else:
return None
## it is the path of a /sys/ subdirectory (e.g.: '/sys/block/hda')
if os.path.isfile(os.path.join(dev, "dev")):
devdir = dev
else:
return None
else:
## the name of a blockdevice (e.g.: 'dm-0')
for one_devdir in find_blockdevices(sysblock_dir):
if os.path.basename(one_devdir) == dev:
devdir = one_devdir
@ -836,6 +865,25 @@ def find_lvm_pv():
return result[:]
def get_sorted_devices(names):
"""return the names of devices in a sorted order
e.g.: "hda1", "hda5", "hda6", ..., "hda10", "hda11"
"""
# TODO: implement this for devicenames like "hda12"
def compare_device_names(x, y):
if x.name < y.name:
return -1
elif x.name == y.name:
return 0
else:
return 1
result = names[:]
result.sort(cmp=compare_device_names)
return result
def _load_preferences():
prefs = cryptobox.core.settings.get_current_settings()
if not prefs is None:

View file

@ -213,6 +213,11 @@ class CryptoBox:
also check, if the device is readable and writeable for the current user
"""
import types
## if "device" is a string, then turn it into a blockdevice object
if type(device) == types.StringType:
device = blockdevice.get_blockdevice(device)
if device is None:
return False
allowed = self.prefs["Main"]["AllowedDevices"]
if type(allowed) == types.StringType:
allowed = [allowed]

View file

@ -1,248 +0,0 @@
#
# Copyright 2006 sense.lab e.V.
#
# This file is part of the CryptoBox.
#
# The CryptoBox 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.
#
# The CryptoBox 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 the CryptoBox; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""Some useful functions for the CryptoBox.
"""
__revision__ = "$Id"
import logging
import os
import re
LOGGER = logging.getLogger("CryptoBox")
def get_available_partitions():
"retrieve a list of all available containers"
ret_list = []
try:
## the following reads all lines of /proc/partitions and adds the mentioned devices
fpart = open("/proc/partitions", "r")
try:
line = fpart.readline()
while line:
p_details = line.split()
if (len(p_details) == 4):
## the following code prevents double entries like /dev/hda and /dev/hda1
(p_major, p_minor, p_size, p_device) = p_details
## ignore lines with: invalid minor/major or extend partitions (size=1)
if re.search('^[0-9]*$', p_major) and \
re.search('^[0-9]*$', p_minor) and (p_size != "1"):
## for some parent devices we have to remove a 'p' (partition)
## an example are partitionable mdadm raid devices (e.g. md1p1)
p_parent = re.sub('p?[1-9]?[0-9]$', '', p_device)
if p_parent == p_device:
if [e for e in ret_list
if re.search('^' + p_parent + 'p?[1-9]?[0-9]$', e)]:
## major partition - its children are already in the list
pass
else:
## major partition - but there are no children for now
ret_list.append(p_device)
else:
## minor partition - remove parent if necessary
if p_parent in ret_list:
ret_list.remove(p_parent)
ret_list.append(p_device)
line = fpart.readline()
finally:
fpart.close()
return [ get_absolute_devicename(e) for e in ret_list ]
except IOError:
LOGGER.warning("Could not read /proc/partitions")
return []
def get_absolute_devicename(shortname):
""" returns the absolute file name of a device (e.g.: "hda1" -> "/dev/hda1")
this does also work for device mapper devices
if the result is non-unique, one arbitrary value is returned
"""
if re.search('^/', shortname):
return shortname
default = os.path.join("/dev", shortname)
if os.path.exists(default):
return default
result = find_major_minor_of_device(shortname)
## if no valid major/minor was found -> exit
if not result:
return default
(major, minor) = result
## for device-mapper devices (major == 254) ...
if major == 254:
result = find_major_minor_device("/dev/mapper", major, minor)
if result:
return result[0]
## now check all files in /dev
result = find_major_minor_device("/dev", major, minor)
if result:
return result[0]
return default
def find_major_minor_of_device(device):
"""Return the major/minor numbers of a block device.
"""
if re.match("/", device) or \
not os.path.exists(os.path.join(os.path.sep, "sys", "block", device)):
## maybe it is an absolute device name
if not os.path.exists(device):
return None
## okay - it seems to to a device node
rdev = os.stat(device).st_rdev
return (os.major(rdev), os.minor(rdev))
blockdev_info_file = os.path.join(os.path.join(
os.path.sep,"sys","block", device), "dev")
try:
f_blockdev_info = open(blockdev_info_file, "r")
blockdev_info = f_blockdev_info.read()
f_blockdev_info.close()
(str_major, str_minor) = blockdev_info.split(":")
## numeric conversion
try:
major = int(str_major)
minor = int(str_minor)
return (major, minor)
except ValueError:
## unknown device numbers -> stop guessing
return None
except IOError:
pass
return None
def find_major_minor_device(dirpath, major, minor):
"""Returns the names of devices with the specified major and minor number.
"""
collected = []
try:
subdirs = [os.path.join(dirpath, e) for e in os.listdir(dirpath)
if (not os.path.islink(os.path.join(dirpath, e))) and \
os.path.isdir(os.path.join(dirpath, e))]
## do a recursive call to parse the directory tree
for dirs in subdirs:
collected.extend(find_major_minor_device(dirs, major, minor))
## filter all device inodes in this directory
collected.extend([os.path.realpath(os.path.join(dirpath, e))
for e in os.listdir(dirpath)
if (os.major(os.stat(os.path.join(dirpath, e)).st_rdev) == major) \
and (os.minor(os.stat(os.path.join(dirpath, e)).st_rdev) == minor)])
## remove double entries
result = []
for item in collected:
if item not in result:
result.append(item)
return result
except OSError:
return []
def get_parent_blockdevices():
"""Return a list of all block devices that contain other devices.
"""
devs = []
for line in file("/proc/partitions"):
p_details = line.split()
## we expect four values - otherwise continue with next iteration
if len(p_details) != 4:
continue
(p_major, p_minor, p_size, p_device) = p_details
## we expect numeric values in the first two columns
if re.search(r'\D', p_major) or re.search(r'\D', p_minor):
continue
## now let us check, if it is a (parent) block device or a partition
if not os.path.isdir(os.path.join(os.path.sep, "sys", "block", p_device)):
continue
devs.append(p_device)
return [ get_absolute_devicename(e) for e in devs ]
def is_part_of_blockdevice(parent, subdevice):
"""Check if the given block device is a parent of 'subdevice'.
e.g. for checking if a partition belongs to a block device
"""
try:
(par_major, par_minor) = find_major_minor_of_device(parent)
(sub_major, sub_minor) = find_major_minor_of_device(subdevice)
except TypeError:
## at least one of these devices did not return a valid major/minor combination
return False
## search the entry below '/sys/block' belonging to the parent
root = os.path.join(os.path.sep, 'sys', 'block')
for bldev in os.listdir(root):
blpath = os.path.join(root, bldev, 'dev')
if os.access(blpath, os.R_OK):
try:
if (str(par_major), str(par_minor)) == tuple([e
for e in file(blpath)][0].strip().split(":",1)):
parent_path = os.path.join(root, bldev)
break
except (IndexError, OSError):
pass
else:
## no block device with this major/minor combination found below '/sys/block'
return False
for subbldev in os.listdir(parent_path):
subblpath = os.path.join(parent_path, subbldev, "dev")
if os.access(subblpath, os.R_OK):
try:
if (str(sub_major), str(sub_minor)) == tuple([e
for e in file(subblpath)][0].strip().split(":",1)):
## the name of the subdevice node is not important - we found it!
return True
except (IndexError, OSError):
pass
return False
def get_blockdevice_size(device):
"""Return the size of a blockdevice in megabyte.
"""
if not device:
return -1
try:
rdev = os.stat(device).st_rdev
except OSError:
return -1
minor = os.minor(rdev)
major = os.major(rdev)
for line in file("/proc/partitions"):
try:
elements = line.split()
if len(elements) != 4:
continue
if (int(elements[0]) == major) and (int(elements[1]) == minor):
return int(elements[2])/1024
except ValueError:
pass
return -1
def get_blockdevice_size_humanly(device):
"""Return a human readable size of a blockdevice.
"""
size = get_blockdevice_size(device)
if size > 5120:
return "%dGB" % int(size/1024)
else:
return "%dMB" % size

View file

@ -324,7 +324,7 @@ class WebInterfaceSites:
## it will get ignored for non-volume plugins
plugin.device = None
if device and self.__set_device(device):
plugin.device = self.cbox.get_container(device).device
plugin.device = self.cbox.get_container(device)
## check the device argument of volume plugins
if "volume" in plugin.plugin_capabilities:
## initialize the dataset of the selected device if necessary