improved implementation of blockdevice cache

added UUID detection for blockdevices
changed interface of cryptobox.core.container and cryptobox.core.main for new blockdevice detection
 * beware: the change is not finished - the cryptobox is not working properly for now
This commit is contained in:
lars 2007-08-17 11:25:51 +00:00
parent b72310097c
commit 35a6570a52
4 changed files with 403 additions and 215 deletions

View file

@ -3,5 +3,5 @@
__revision__ = "$Id$" __revision__ = "$Id$"
__all__ = [ 'main', 'container', 'exceptions', 'tools', 'settings' ] __all__ = [ 'main', 'container', 'exceptions', 'blockdevice', 'settings' ]

View file

@ -25,13 +25,13 @@ These classes detect and filter available blockdevices.
__revision__ = "$Id$" __revision__ = "$Id$"
#TODO: use logger to report interesting behaviour
import os import os
import subprocess import subprocess
import time import time
import logging
import cryptobox.core.settings import cryptobox.core.settings
LOGGER = logging.getLogger("CryptoBox")
DEFAULT_SYSBLOCK_DIR = '/sys/block' DEFAULT_SYSBLOCK_DIR = '/sys/block'
DEFAULT_DEVNODE_DIR = '/dev' DEFAULT_DEVNODE_DIR = '/dev'
@ -40,14 +40,18 @@ MAJOR_DEVNUM_RAM = 1
MAJOR_DEVNUM_LOOP = 7 MAJOR_DEVNUM_LOOP = 7
MAJOR_DEVNUM_MD_RAID = 9 MAJOR_DEVNUM_MD_RAID = 9
USE_CACHE = True ## cache settings
CACHE_ENABLED = True
CACHE_EXPIRE_SECONDS = 60 CACHE_EXPIRE_SECONDS = 60
CACHE_MONITOR_FILE = '/proc/partitions'
#TODO: remove this after profiling ## useful for manual profiling
IS_VISIBLE = True IS_VISIBLE = True
## caching is quite important for the following implementation ## caching is quite important for the following implementation
CACHED_VALUES = {} ## the object will be initializes later below
CACHE = None
class Blockdevices: class Blockdevices:
"""handle all blockdevices of this system """handle all blockdevices of this system
@ -72,6 +76,18 @@ class Blockdevices:
return self.devices[:] return self.devices[:]
def get_storage_devices(self):
"""return a list of devices with the 'storage' flag
"""
return [ dev for dev in self.devices if dev.is_storage() ]
def get_partitionable_devices(self):
"""return a list of devices with the 'partitionable' flag
"""
return [ dev for dev in self.devices if dev.is_partitionable() ]
class Blockdevice: class Blockdevice:
@ -84,17 +100,40 @@ class Blockdevice:
self.devnode_dir = devnode_dir self.devnode_dir = devnode_dir
self.sysblock_dir = sysblock_dir self.sysblock_dir = sysblock_dir
self.name = os.path.basename(self.devdir) self.name = os.path.basename(self.devdir)
## "reset" below will fill these values
self.devnum = None
self.size = None
self.size_human = None
self.range = None
self.slaves = None
self.holders = None
self.children = None
self.devnodes = None
self.uuid = None
self.label = None
self.reset()
def reset(self):
"""reread the data of the device
"""
CACHE.reset(["blockdevice_info", self.name])
self.devnum = self.__get_major_minor() self.devnum = self.__get_major_minor()
self.size = self.__get_size() self.size = self.__get_size()
self.size_human = self.__get_size_human()
self.range = self.__get_device_range() self.range = self.__get_device_range()
self.slaves = self.__get_dev_related("slaves") self.slaves = self.__get_dev_related("slaves")
self.holders = self.__get_dev_related("holders") self.holders = self.__get_dev_related("holders")
self.children = self.__get_children() self.children = self.__get_children()
self.devnodes = self.__get_device_nodes() self.devnodes = self.__get_device_nodes()
self.uuid = self.__get_uuid()
self.label = self.__get_label()
def is_valid(self): def is_valid(self):
""" check if the device is usable and valid """check if the device is usable and valid
causes of invalidity: ram device, loop device, removable device
""" """
if not self.devnodes: if not self.devnodes:
return False return False
@ -109,6 +148,9 @@ class Blockdevice:
## loop devices are ignored ## loop devices are ignored
if major == MAJOR_DEVNUM_LOOP: if major == MAJOR_DEVNUM_LOOP:
return False return False
## removable devices are ignored (due to long timeouts)
if self.is_removable():
return False
except TypeError: except TypeError:
return False return False
return True return True
@ -119,38 +161,38 @@ class Blockdevice:
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", self.name, "is_storage"] cache_link = ["blockdevice_info", self.name, "is_storage"]
cached = _get_cached_value(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
if self.range > 1: if self.range > 1:
## partitionable blockdevice ## partitionable blockdevice
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
if self.size < MINIMUM_STORAGE_SIZE: if self.size < MINIMUM_STORAGE_SIZE:
## extended partition, unused loop device ## extended partition, unused loop device
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
if self.devnum[0] == MAJOR_DEVNUM_RAM: if self.devnum[0] == MAJOR_DEVNUM_RAM:
## ram device ## ram device
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
## are we the device mapper of a luks device? ## are we the device mapper of a luks device?
for slave in self.slaves: for slave in self.slaves:
if get_blockdevice(slave, self.sysblock_dir, if get_blockdevice(slave, self.sysblock_dir,
self.devnode_dir).is_luks(): self.devnode_dir).is_luks():
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
## if we are a luks device with exactly one child, then ## if we are a luks device with exactly one child, then
## we are a storage ## we are a storage
if (len(self.children) == 1) and self.is_luks(): if (len(self.children) == 1) and self.is_luks():
_set_cached_value(cache_link, True) CACHE.set(cache_link, True)
return True return True
if self.children: if self.children:
## a parent blockdevice ## a parent blockdevice
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
_set_cached_value(cache_link, True) CACHE.set(cache_link, True)
return True return True
@ -168,16 +210,16 @@ class Blockdevice:
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", self.name, "is_lvm_pv"] cache_link = ["blockdevice_info", self.name, "is_lvm_pv"]
cached = _get_cached_value(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
## is one of the devnodes of the device a physical volume? ## is one of the devnodes of the device a physical volume?
for one_lvm_pv in find_lvm_pv(): for one_lvm_pv in find_lvm_pv():
if one_lvm_pv in self.devnodes: if one_lvm_pv in self.devnodes:
_set_cached_value(cache_link, True) CACHE.set(cache_link, True)
return True return True
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
@ -186,23 +228,23 @@ class Blockdevice:
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", self.name, "is_lvm_lv"] cache_link = ["blockdevice_info", self.name, "is_lvm_lv"]
cached = _get_cached_value(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
## is one of the devnodes of the device a physical volume? ## is one of the devnodes of the device a physical volume?
## logical LVM volumes always depend on their physical volumes ## logical LVM volumes always depend on their physical volumes
if not self.slaves: if not self.slaves:
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
## is one of the LVM physical volumes a device node of our slave(s)? ## is one of the LVM physical volumes a device node of our slave(s)?
for one_lvm_pv in find_lvm_pv(): for one_lvm_pv in find_lvm_pv():
for one_slave in self.slaves: for one_slave in self.slaves:
if one_lvm_pv in get_blockdevice(one_slave, if one_lvm_pv in get_blockdevice(one_slave,
self.sysblock_dir, self.devnode_dir).devnodes: self.sysblock_dir, self.devnode_dir).devnodes:
_set_cached_value(cache_link, True) CACHE.set(cache_link, True)
return True return True
_set_cached_value(cache_link, False) CACHE.set(cache_link, False)
return False return False
@ -211,7 +253,7 @@ class Blockdevice:
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", self.name, "is_md_raid"] cache_link = ["blockdevice_info", self.name, "is_md_raid"]
cached = _get_cached_value(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -229,7 +271,7 @@ class Blockdevice:
result = False result = False
## store result and return ## store result and return
_set_cached_value(cache_link, result) CACHE.set(cache_link, result)
return result return result
@ -238,7 +280,7 @@ class Blockdevice:
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", self.name, "is_luks"] cache_link = ["blockdevice_info", self.name, "is_luks"]
cached = _get_cached_value(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -262,7 +304,33 @@ class Blockdevice:
proc.wait() proc.wait()
result = proc.returncode == 0 result = proc.returncode == 0
## store result and return ## store result and return
_set_cached_value(cache_link, result) CACHE.set(cache_link, result)
return result
def is_removable(self):
"""check if the device is marked as 'removable'
"""
## check the cache first
cache_link = ["blockdevice_info", self.name, "is_removable"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
removable_file = os.path.join(self.devdir, "removable")
if os.path.isfile(removable_file):
try:
content = file(removable_file).read().strip()
if content == "1":
return True
else:
return False
except IOError:
result = False
else:
result = False
CACHE.set(cache_link, result)
return result return result
@ -275,12 +343,23 @@ class Blockdevice:
return [] return []
def __get_size_human(self):
"""return a human readable string representing the size of the device
"""
size = self.size
if self.size > 5120:
return "%dGB" % int(self.size/1024)
else:
return "%dMB" % self.size
def __get_size(self): def __get_size(self):
"""return the size (in kB) of the blockdevice """return the size (in MB) of the blockdevice
""" """
default = 0 default = 0
try: try:
return int(file(os.path.join(self.devdir, 'size')).read()) size_kb = int(file(os.path.join(self.devdir, 'size')).read())
return int(size_kb/1024)
except OSError: except OSError:
return default return default
except ValueError: except ValueError:
@ -358,6 +437,150 @@ class Blockdevice:
return result return result
def __get_uuid(self):
"""determine the unique identifier of this device
returns None in case of error or for invalid devices (see "is_valid")
"""
if not self.is_valid():
return None
## partitionable devices do not have a UUID
if self.is_partitionable():
return None
## UUIDs of physical LVM volumes can only be determined via pvdisplay
if self.is_lvm_pv():
return self.__get_uuid_lvm_pv()
## UUIDs of luks devices can be determined via luksDump
if self.is_luks():
return self.__get_uuid_luks()
return self.__get_uuid_default()
def __get_uuid_luks(self):
"""determine the unique identifier of luks devices
"""
prefs = _load_preferences()
try:
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [ prefs["Programs"]["cryptsetup"],
"luksUUID", self.devnodes[0] ])
proc.wait()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' to determine UUID: %s" \
% (prefs["Programs"]["cryptsetup"], err_msg))
return None
if proc.returncode != 0:
LOGGER.warning("Execution of '%s' failed: %s" % \
(prefs["Programs"]["cryptsetup"],
proc.stderr.read().strip()))
return None
result = proc.stdout.read().strip()
if result:
return result
else:
return None
def __get_uuid_lvm_pv(self):
"""determine the unique identifier of physical LVM volumes
"""
prefs = _load_preferences()
try:
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [ prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"],
"program", "pvdisplay" ])
proc.wait()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' via 'super' to determine " \
% prefs["Programs"]["pvdisplay"] + "UUID: %s" % err_msg)
return None
if proc.returncode != 0:
LOGGER.warning("Execution of 'pvdisplay' failed: %s" % \
proc.stderr.read().strip())
return None
for line in proc.stdout.readlines():
items = line.strip().split(":")
if (len(items) == 12) and (items[0] in self.devnodes):
return items[11]
## not found
return None
def __get_uuid_default(self):
"""determine the unique identifier for non-special devices
luks and lvm_pv devices must be treated differently
"""
prefs = _load_preferences()
try:
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [ prefs["Programs"]["blkid"],
"-s", "UUID",
"-o", "value",
"-c", os.devnull,
"-w", os.devnull,
self.devnodes[0] ])
proc.wait()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' to determine UUID: %s" % \
(prefs["Programs"]["blkid"], err_msg))
return None
if proc.returncode != 0:
LOGGER.warning("Execution of '%s' failed: %s" % \
(prefs["Programs"]["blkid"], proc.stderr.read().strip()))
return None
result = proc.stdout.read().strip()
if result:
return result
else:
return None
def __get_label(self):
"""determine the label of a filesystem contained in a device
return None for errors, empty labels and for non-storage devices
"""
if not self.is_valid():
return None
prefs = _load_preferences()
try:
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [ prefs["Programs"]["blkid"],
"-s", "LABEL",
"-o", "value",
"-c", os.devnull,
"-w", os.devnull,
self.devnodes[0]])
proc.wait()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' to determine label: %s" % \
(prefs["Programs"]["blkid"], err_msg))
return None
if proc.returncode != 0:
LOGGER.warning("Execution of '%s' failed: %s" % \
(prefs["Programs"]["blkid"], proc.stderr.read().strip()))
return None
result = proc.stdout.read().strip()
if result:
return result
else:
return None
def __str__(self): def __str__(self):
"""display the name of the device """display the name of the device
""" """
@ -370,6 +593,8 @@ class Blockdevice:
output = "%s:\n" % self.name output = "%s:\n" % self.name
output += "\t%s:\t%s\n" % ("blockdir", self.devdir) output += "\t%s:\t%s\n" % ("blockdir", self.devdir)
output += "\t%s:\t%s\n" % ("major/minor", self.devnum) output += "\t%s:\t%s\n" % ("major/minor", self.devnum)
output += "\t%s:\t\t%s\n" % ("label", self.label)
output += "\t%s:\t\t%s\n" % ("UUID", self.uuid)
output += "\t%s:\t\t%s\n" % ("range", self.range) output += "\t%s:\t\t%s\n" % ("range", self.range)
output += "\t%s:\t\t%s\n" % ("size", self.size) output += "\t%s:\t\t%s\n" % ("size", self.size)
output += "\t%s:\t\t%s\n" % ("slaves", self.slaves) output += "\t%s:\t\t%s\n" % ("slaves", self.slaves)
@ -377,14 +602,98 @@ class Blockdevice:
output += "\t%s:\t%s\n" % ("children", self.children) output += "\t%s:\t%s\n" % ("children", self.children)
output += "\t%s:\t%s\n" % ("device nodes", self.devnodes) output += "\t%s:\t%s\n" % ("device nodes", self.devnodes)
output += "\tflags:\t\t" output += "\tflags:\t\t"
for funcname in [ "storage", "md_raid", "partitionable", "luks", for funcname in [ func for func in dir(self)
"lvm_pv", "lvm_lv"]: if func.startswith("is_") and callable(getattr(self, func))]:
if getattr(self, "is_%s" % funcname)(): if getattr(self, funcname)():
output += "%s " % funcname output += "%s " % funcname[3:]
output += "\n" output += "\n"
return output return output
class BlockdeviceCache:
"""manage cached results of blockdevce queries
the cache expires every 60 seconds or as soon as CACHE_MONITOR_FILE changes
"""
def __init__(self):
self.values = {}
self.expires = None
self.partitions_save = None
self.reset()
def reset(self, link=None):
"""empty the cache and reset the expire time
"""
if not link:
self.values = {}
try:
self.partitions_save = file(CACHE_MONITOR_FILE).read()
except IOError, err_msg:
LOGGER.warning("Failed to read '%s': %s" % \
(CACHE_MONITOR_FILE, err_msg))
self.partitions_save = ""
self.expires = int(time.time()) + CACHE_EXPIRE_SECONDS
else:
## we do no reset the expire date
self.set(link, {})
def __is_expired(self):
"""check if the cache is expired
"""
try:
if (file(CACHE_MONITOR_FILE).read() != self.partitions_save) or \
(self.expires < int(time.time())):
return True
except IOError:
LOGGER.warning("Failed to read '%s': %s" % \
(CACHE_MONITOR_FILE, err_msg))
return False
def get(self, link):
"""return a cached value
"link" is an array of the hierachie of the accessed item
e.g. link = ["blockdevices", "hda"]
return None if the value is not in the cache or if CACHE_ENABLED is False
"""
if not CACHE_ENABLED:
return None
if self.__is_expired():
self.reset()
## walk down the tree
ref = self.values
for element in link:
if element in ref:
ref = ref[element]
else:
return None
return ref
def set(self, link, item):
"""store an item in the cache
"link" is an array of the hierachie of the accessed item
e.g. link = ["blockdevices", "hda"]
"""
if not CACHE_ENABLED:
return
## walk down the tree
ref = self.values
for element in link[:-1]:
if not element in ref:
## create a non-existing sub element
ref[element] = {}
ref = ref[element]
## store the item
ref[link[-1]] = item
def get_blockdevice(dev, def get_blockdevice(dev,
sysblock_dir=DEFAULT_SYSBLOCK_DIR, sysblock_dir=DEFAULT_SYSBLOCK_DIR,
devnode_dir=DEFAULT_DEVNODE_DIR): devnode_dir=DEFAULT_DEVNODE_DIR):
@ -401,16 +710,18 @@ def get_blockdevice(dev,
else: else:
return None return None
devname = os.path.basename(devdir) devname = os.path.basename(devdir)
dev = _get_cached_value(["blockdevices", devname]) cache_link = ["blockdevices", devname]
dev = CACHE.get(cache_link)
if dev is None: if dev is None:
dev = Blockdevice(devdir, sysblock_dir, devnode_dir) dev = Blockdevice(devdir, sysblock_dir, devnode_dir)
_set_cached_value(["blockdevices", devname], dev) CACHE.set(cache_link, dev)
return dev return dev
def find_blockdevices(top_dir): def find_blockdevices(top_dir):
cached = _get_cached_value(["blockdevice_dirs", top_dir]) cache_link = ["blockdevice_dirs", top_dir]
cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached[:] return cached[:]
@ -431,98 +742,44 @@ def find_blockdevices(top_dir):
fnames.remove(fname) fnames.remove(fname)
os.path.walk(top_dir, look4dev_dirs, 'dev') os.path.walk(top_dir, look4dev_dirs, 'dev')
_set_cached_value(["blockdevice_dirs", top_dir], dev_dirs) CACHE.set(cache_link, dev_dirs)
return dev_dirs[:] return dev_dirs[:]
def find_lvm_pv(): def find_lvm_pv():
"""return the blockdevice names of all physical LVM volumes """return the blockdevice names of all physical LVM volumes
""" """
cached = _get_cached_value(["lvm", "pv"]) cache_link = ["lvm", "pv"]
cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached[:] return cached[:]
#TODO: should we check, if LVM is supported at all?
# e.g. by checking the existence of pvdisplay?
prefs = _load_preferences() prefs = _load_preferences()
result = None result = None
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
shell = False, shell = False,
stdout = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [ prefs["Programs"]["super"], args = [ prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"], prefs["Programs"]["CryptoBoxRootActions"],
"program", "pvdisplay" ]) "program", "pvdisplay" ])
proc.wait() proc.wait()
except OSError, err_msg: except OSError, err_msg:
# TODO: add a logging warning LOGGER.info("Failed to call 'pvdisplay' via 'super': %s" % err_msg)
result = [] result = []
if proc.returncode != 0: if proc.returncode != 0:
# TODO: add a logging warning LOGGER.info("Execution of 'pvdisplay' failed: %s" % \
proc.stderr.read().strip())
result = [] result = []
if result is None: if result is None:
result = [] result = []
for line in proc.stdout.readlines(): for line in proc.stdout.readlines():
result.append(line.split(":", 1)[0].strip()) result.append(line.split(":", 1)[0].strip())
_set_cached_value(["lvm", "pv"], result) CACHE.set(cache_link, result)
return result[:] return result[:]
def _get_cached_value(link):
"""return a cached value
"link" is an array of the hierachie of the accessed item
e.g. link = ["blockdevices", "hda"]
return None if the value is not in the cache or if USE_CACHE is False
"""
if not USE_CACHE:
return None
if "expires" in CACHED_VALUES:
if CACHED_VALUES["expires"] < int(time.time()):
reset_cache()
else:
__reset_cache_timer()
ref = CACHED_VALUES
for element in link:
if element in ref:
ref = ref[element]
else:
return None
return ref
def reset_cache():
## refresh the cache
for item in CACHED_VALUES:
CACHED_VALUES[item] = {}
__reset_cache_timer()
def __reset_cache_timer():
CACHED_VALUES["expires"] = int(time.time()) + CACHE_EXPIRE_SECONDS
def _set_cached_value(link, item):
"""store an item in the cache
"link" is an array of the hierachie of the accessed item
e.g. link = ["blockdevices", "hda"]
"""
if not USE_CACHE:
return
ref = CACHED_VALUES
for element in link[:-1]:
if not element in ref:
## create a non-existing sub element
ref[element] = {}
ref = ref[element]
## store the item
ref[link[-1]] = item
def _load_preferences(): def _load_preferences():
prefs = cryptobox.core.settings.get_current_settings() prefs = cryptobox.core.settings.get_current_settings()
if not prefs is None: if not prefs is None:
@ -540,6 +797,10 @@ def _load_preferences():
## initialize cache
CACHE = BlockdeviceCache()
if __name__ == '__main__': if __name__ == '__main__':
## list the properties of all available devices ## list the properties of all available devices
## this is just for testing purposes ## this is just for testing purposes

View file

@ -1,5 +1,5 @@
# #
# Copyright 2006 sense.lab e.V. # Copyright 02006-02007 sense.lab e.V.
# #
# This file is part of the CryptoBox. # This file is part of the CryptoBox.
# #
@ -55,6 +55,10 @@ class CryptoBoxContainer:
def __init__(self, device, cbox): def __init__(self, device, cbox):
"""initialize the container
"device" is a cryptobox.core.blockdevice object
"""
self.device = device self.device = device
self.cbox = cbox self.cbox = cbox
self.uuid = None self.uuid = None
@ -149,7 +153,7 @@ class CryptoBoxContainer:
e.g.: /dev/hdc1 e.g.: /dev/hdc1
Available since: 0.3.0 Available since: 0.3.0
""" """
return self.device return self.device.devnodes[0]
def get_type(self): def get_type(self):
@ -198,8 +202,7 @@ class CryptoBoxContainer:
an error is indicated by "-1" an error is indicated by "-1"
Available since: 0.3.0 Available since: 0.3.0
""" """
import cryptobox.core.tools as cbxtools return self.device.size
return cbxtools.get_blockdevice_size(self.device)
def reset_object(self): def reset_object(self):
@ -208,6 +211,7 @@ class CryptoBoxContainer:
this is especially useful after changing the type via 'create' this is especially useful after changing the type via 'create'
Available since: 0.3.0 Available since: 0.3.0
""" """
self.device.reset()
self.uuid = self.__get_uuid() self.uuid = self.__get_uuid()
self.cont_type = self.__get_type_of_partition() self.cont_type = self.__get_type_of_partition()
self.fs_type = self.__get_fs_type() self.fs_type = self.__get_fs_type()
@ -277,7 +281,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["CryptoBoxRootActions"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"luksAddKey", "luksAddKey",
self.device, self.device.devnodes[0],
"--batch-mode"]) "--batch-mode"])
proc.stdin.write("%s\n%s" % (oldpw, newpw)) proc.stdin.write("%s\n%s" % (oldpw, newpw))
(output, errout) = proc.communicate() (output, errout) = proc.communicate()
@ -302,7 +306,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["cryptsetup"], self.cbox.prefs["Programs"]["cryptsetup"],
"--batch-mode", "--batch-mode",
"luksDelKey", "luksDelKey",
self.device, self.device.devnodes[0],
"%d" % (keyslot, )]) "%d" % (keyslot, )])
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
@ -318,7 +322,7 @@ class CryptoBoxContainer:
while it is being formatted or similar. while it is being formatted or similar.
Available since: 0.3.1 Available since: 0.3.1
""" """
return self.cbox.get_device_busy_state(self.device) return self.cbox.get_device_busy_state(self.device.name)
def set_busy(self, new_state, timeout=300): def set_busy(self, new_state, timeout=300):
@ -328,7 +332,7 @@ class CryptoBoxContainer:
The timeout is optional and defaults to five minutes. The timeout is optional and defaults to five minutes.
Available since: 0.3.1 Available since: 0.3.1
""" """
self.cbox.set_device_busy_state(self.device, new_state, timeout) self.cbox.set_device_busy_state(self.device.name, new_state, timeout)
## ****************** internal stuff ********************* ## ****************** internal stuff *********************
@ -358,56 +362,13 @@ class CryptoBoxContainer:
def __get_uuid(self): def __get_uuid(self):
"""Retrieve the uuid of the container device. """Retrieve the uuid of the container device.
""" """
if self.__get_type_of_partition() == CONTAINERTYPES["luks"]: guess = self.device.uuid
guess = self.__get_luks_uuid()
else:
guess = self.__get_non_luks_uuid()
## did we get a valid value? ## did we get a valid value?
if guess: if guess:
return guess return guess
else: else:
## emergency default value ## emergency default value
return self.device.replace(os.path.sep, "_") return self.device.name
def __get_luks_uuid(self):
"""get uuid for luks devices"""
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [self.cbox.prefs["Programs"]["cryptsetup"],
"luksUUID",
self.device])
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
self.cbox.log.info("could not retrieve luks uuid (%s): %s",
(self.device, stderr.strip()))
return None
return stdout.strip()
def __get_non_luks_uuid(self):
"""return UUID for ext2/3 and vfat filesystems"""
proc = subprocess.Popen(
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
args=[self.cbox.prefs["Programs"]["blkid"],
"-s", "UUID",
"-o", "value",
"-c", os.devnull,
"-w", os.devnull,
self.device])
(stdout, stderr) = proc.communicate()
## execution failed?
if proc.returncode != 0:
self.cbox.log.info("retrieving of partition type (" + str(self.device) \
+ ") via 'blkid' failed: " + str(stderr.strip()) \
+ " - maybe it is encrypted?")
return None
## return output of blkid
return stdout.strip()
def __get_type_of_partition(self): def __get_type_of_partition(self):
@ -415,7 +376,7 @@ class CryptoBoxContainer:
see cryptobox.core.container.CONTAINERTYPES see cryptobox.core.container.CONTAINERTYPES
""" """
if self.__is_luks_partition(): if self.device.is_luks():
return CONTAINERTYPES["luks"] return CONTAINERTYPES["luks"]
type_of_partition = self.__get_type_id_of_partition() type_of_partition = self.__get_type_id_of_partition()
if type_of_partition in FSTYPES["plain"]: if type_of_partition in FSTYPES["plain"]:
@ -436,7 +397,7 @@ class CryptoBoxContainer:
"-o", "value", "-o", "value",
"-c", os.devnull, "-c", os.devnull,
"-w", os.devnull, "-w", os.devnull,
self.device ]) self.device.devnodes[0] ])
(stdout, stderr) = proc.communicate() (stdout, stderr) = proc.communicate()
if proc.returncode == 0: if proc.returncode == 0:
## we found a uuid ## we found a uuid
@ -454,13 +415,14 @@ class CryptoBoxContainer:
def __get_fs_type(self): def __get_fs_type(self):
"returns the filesystem used on a container" "returns the filesystem used on a container"
## should we handle device mapping or plain device ## should we handle device mapping or plain device
if self.__is_luks_partition() and self.name: if self.device.is_luks() and self.name:
#TODO: replace this by self.device.holders ...
container = os.path.join(self.__dmDir, self.name) container = os.path.join(self.__dmDir, self.name)
## can't determine fs while encrypted ## can't determine fs while encrypted
if not self.is_mounted(): if not self.is_mounted():
return "unavailable" return "unavailable"
else: else:
container = self.device container = self.device.devnodes[0]
proc = subprocess.Popen( proc = subprocess.Popen(
shell = False, shell = False,
stdout = subprocess.PIPE, stdout = subprocess.PIPE,
@ -479,22 +441,6 @@ class CryptoBoxContainer:
return return
def __is_luks_partition(self):
"check if the given device is a luks partition"
proc = subprocess.Popen(
shell = False,
stdin = None,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [
self.cbox.prefs["Programs"]["cryptsetup"],
"--batch-mode",
"isLuks",
self.device])
proc.wait()
return proc.returncode == 0
def __get_mount_point(self): def __get_mount_point(self):
"return the name of the mountpoint of this volume" "return the name of the mountpoint of this volume"
return os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], self.name) return os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], self.name)
@ -525,7 +471,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["CryptoBoxRootActions"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"luksOpen", "luksOpen",
self.device, self.device.devnodes[0],
self.name, self.name,
"--batch-mode"]) "--batch-mode"])
proc.stdin.write(password) proc.stdin.write(password)
@ -621,7 +567,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["super"], self.cbox.prefs["Programs"]["super"],
self.cbox.prefs["Programs"]["CryptoBoxRootActions"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
"program", "mount", "program", "mount",
self.device, self.device.devnodes[0],
self.__get_mount_point()]) self.__get_mount_point()])
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
@ -691,7 +637,7 @@ class CryptoBoxContainer:
args = [ args = [
self.cbox.prefs["Programs"]["nice"], self.cbox.prefs["Programs"]["nice"],
self.cbox.prefs["Programs"]["mkfs"], self.cbox.prefs["Programs"]["mkfs"],
"-t", fs_type, self.device]) "-t", fs_type, self.device.devnodes[0]])
loc_data.proc.wait() loc_data.proc.wait()
## wait to allow error detection ## wait to allow error detection
if loc_data.proc.returncode == 0: if loc_data.proc.returncode == 0:
@ -711,7 +657,7 @@ class CryptoBoxContainer:
time.sleep(3) time.sleep(3)
## if the thread exited very fast, then it failed ## if the thread exited very fast, then it failed
if not bg_task.isAlive(): if not bg_task.isAlive():
raise CBCreateError("formatting of device (%s) failed out " % self.device \ raise CBCreateError("formatting of device (%s) failed out " % self.device.devnodes[0] \
+ "of unknown reasons") + "of unknown reasons")
@ -736,7 +682,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["CryptoBoxRootActions"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"luksFormat", "luksFormat",
self.device, self.device.devnodes[0],
"--batch-mode", "--batch-mode",
"--cipher", self.cbox.prefs["Main"]["DefaultCipher"], "--cipher", self.cbox.prefs["Main"]["DefaultCipher"],
"--iter-time", "2000"]) "--iter-time", "2000"])
@ -757,7 +703,7 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["CryptoBoxRootActions"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"luksOpen", "luksOpen",
self.device, self.device.devnodes[0],
self.name, self.name,
"--batch-mode"]) "--batch-mode"])
proc.stdin.write(password) proc.stdin.write(password)
@ -808,7 +754,7 @@ class CryptoBoxContainer:
time.sleep(3) time.sleep(3)
## if the thread exited very fast, then it failed ## if the thread exited very fast, then it failed
if not bg_task.isAlive(): if not bg_task.isAlive():
raise CBCreateError("formatting of device (%s) failed out " % self.device \ raise CBCreateError("formatting of device (%s) failed out " % self.device.devnodes[0] \
+ "of unknown reasons") + "of unknown reasons")

View file

@ -1,5 +1,5 @@
# #
# Copyright 2006 sense.lab e.V. # Copyright 02006-02007 sense.lab e.V.
# #
# This file is part of the CryptoBox. # This file is part of the CryptoBox.
# #
@ -27,9 +27,8 @@ __revision__ = "$Id$"
import sys import sys
import cryptobox.core.container as cbxContainer import cryptobox.core.container as cbxContainer
from cryptobox.core.exceptions import CBEnvironmentError, CBConfigUndefinedError from cryptobox.core.exceptions import CBEnvironmentError, CBConfigUndefinedError
import re import cryptobox.core.blockdevice as blockdevice
import os import os
import cryptobox.core.tools as cbxTools
import subprocess import subprocess
import threading import threading
@ -154,7 +153,8 @@ class CryptoBox:
""" """
self.log.debug("rereading container list") self.log.debug("rereading container list")
self.__containers = [] self.__containers = []
for device in cbxTools.get_available_partitions(): blockdevice.CACHE.reset()
for device in blockdevice.Blockdevices().get_storage_devices():
if self.is_device_allowed(device) and not self.is_config_partition(device): if self.is_device_allowed(device) and not self.is_config_partition(device):
self.__containers.append(cbxContainer.CryptoBoxContainer(device, self)) self.__containers.append(cbxContainer.CryptoBoxContainer(device, self))
## sort by container name ## sort by container name
@ -204,49 +204,30 @@ class CryptoBox:
The check is done by comparing the label of the filesystem with a string. The check is done by comparing the label of the filesystem with a string.
""" """
proc = subprocess.Popen( return device.label == self.prefs["Main"]["ConfigVolumeLabel"]
shell = False,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
args = [
self.prefs["Programs"]["blkid"],
"-c", os.path.devnull,
"-o", "value",
"-s", "LABEL",
device])
(output, error) = proc.communicate()
return output.strip() == self.prefs["Main"]["ConfigVolumeLabel"]
def is_device_allowed(self, devicename): def is_device_allowed(self, device):
"""check if a device is white-listed for being used as cryptobox containers """check if a device is white-listed for being used as cryptobox containers
also check, if the device is readable and writeable for the current user also check, if the device is readable and writeable for the current user
""" """
import types import types
devicename = os.path.abspath(devicename)
if not os.access(devicename, os.R_OK):
self.log.debug("Skipping device without read permissions: %s" % devicename)
return False
if not os.access(devicename, os.W_OK):
self.log.debug("Skipping device without write permissions: %s" % devicename)
return False
allowed = self.prefs["Main"]["AllowedDevices"] allowed = self.prefs["Main"]["AllowedDevices"]
if type(allowed) == types.StringType: if type(allowed) == types.StringType:
allowed = [allowed] allowed = [allowed]
for a_dev in allowed: for devnode in device.devnodes:
if not a_dev: if [ a_dev for a_dev in allowed if devnode.startswith(a_dev) ]:
continue if os.access(devnode, os.R_OK | os.W_OK):
## double dots are not allowed (e.g. /dev/ide/../sda) self.log.debug("Adding valid device: %s" % devnode)
if re.search("/\.\./", devicename): ## move the device to the first position
continue device.devnodes.remove(devnode)
## it is not possible to check for 'realpath' - that does not work device.devnodes.insert(0, devnode)
## for the cryptobox as /dev/ is bind-mounted (real hda-name is /opt/...) return True
if re.search('^%s' % a_dev, devicename): else:
self.log.debug("Adding valid device: %s" % devicename) self.log.debug("Skipping device without read and write" \
return True + "permissions: %s" % device.name)
self.log.debug("Skipping device not listed in Main->AllowedDevices: %s" \ self.log.debug("Skipping unusable device: %s" % device.name)
% devicename)
return False return False
@ -269,7 +250,7 @@ class CryptoBox:
def get_container(self, device): def get_container(self, device):
"retrieve the container element for this device" "retrieve the container element for this device"
all = [e for e in self.get_container_list() if e.device == device] all = [e for e in self.get_container_list() if e.device.name == device.name]
if all: if all:
return all[0] return all[0]
else: else: