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:
parent
b72310097c
commit
35a6570a52
4 changed files with 403 additions and 215 deletions
|
@ -3,5 +3,5 @@
|
|||
|
||||
__revision__ = "$Id$"
|
||||
|
||||
__all__ = [ 'main', 'container', 'exceptions', 'tools', 'settings' ]
|
||||
__all__ = [ 'main', 'container', 'exceptions', 'blockdevice', 'settings' ]
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ These classes detect and filter available blockdevices.
|
|||
__revision__ = "$Id$"
|
||||
|
||||
|
||||
#TODO: use logger to report interesting behaviour
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import logging
|
||||
import cryptobox.core.settings
|
||||
|
||||
LOGGER = logging.getLogger("CryptoBox")
|
||||
|
||||
DEFAULT_SYSBLOCK_DIR = '/sys/block'
|
||||
DEFAULT_DEVNODE_DIR = '/dev'
|
||||
|
@ -40,14 +40,18 @@ MAJOR_DEVNUM_RAM = 1
|
|||
MAJOR_DEVNUM_LOOP = 7
|
||||
MAJOR_DEVNUM_MD_RAID = 9
|
||||
|
||||
USE_CACHE = True
|
||||
## cache settings
|
||||
CACHE_ENABLED = True
|
||||
CACHE_EXPIRE_SECONDS = 60
|
||||
CACHE_MONITOR_FILE = '/proc/partitions'
|
||||
|
||||
#TODO: remove this after profiling
|
||||
## useful for manual profiling
|
||||
IS_VISIBLE = True
|
||||
|
||||
## caching is quite important for the following implementation
|
||||
CACHED_VALUES = {}
|
||||
## the object will be initializes later below
|
||||
CACHE = None
|
||||
|
||||
|
||||
class Blockdevices:
|
||||
"""handle all blockdevices of this system
|
||||
|
@ -72,6 +76,18 @@ class Blockdevices:
|
|||
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:
|
||||
|
||||
|
@ -84,17 +100,40 @@ class Blockdevice:
|
|||
self.devnode_dir = devnode_dir
|
||||
self.sysblock_dir = sysblock_dir
|
||||
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.size = self.__get_size()
|
||||
self.size_human = self.__get_size_human()
|
||||
self.range = self.__get_device_range()
|
||||
self.slaves = self.__get_dev_related("slaves")
|
||||
self.holders = self.__get_dev_related("holders")
|
||||
self.children = self.__get_children()
|
||||
self.devnodes = self.__get_device_nodes()
|
||||
self.uuid = self.__get_uuid()
|
||||
self.label = self.__get_label()
|
||||
|
||||
|
||||
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:
|
||||
return False
|
||||
|
@ -109,6 +148,9 @@ class Blockdevice:
|
|||
## loop devices are ignored
|
||||
if major == MAJOR_DEVNUM_LOOP:
|
||||
return False
|
||||
## removable devices are ignored (due to long timeouts)
|
||||
if self.is_removable():
|
||||
return False
|
||||
except TypeError:
|
||||
return False
|
||||
return True
|
||||
|
@ -119,38 +161,38 @@ class Blockdevice:
|
|||
"""
|
||||
## check the cache first
|
||||
cache_link = ["blockdevice_info", self.name, "is_storage"]
|
||||
cached = _get_cached_value(cache_link)
|
||||
cached = CACHE.get(cache_link)
|
||||
if not cached is None:
|
||||
return cached
|
||||
|
||||
if self.range > 1:
|
||||
## partitionable blockdevice
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
if self.size < MINIMUM_STORAGE_SIZE:
|
||||
## extended partition, unused loop device
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
if self.devnum[0] == MAJOR_DEVNUM_RAM:
|
||||
## ram device
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
## are we the device mapper of a luks device?
|
||||
for slave in self.slaves:
|
||||
if get_blockdevice(slave, self.sysblock_dir,
|
||||
self.devnode_dir).is_luks():
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
## if we are a luks device with exactly one child, then
|
||||
## we are a storage
|
||||
if (len(self.children) == 1) and self.is_luks():
|
||||
_set_cached_value(cache_link, True)
|
||||
CACHE.set(cache_link, True)
|
||||
return True
|
||||
if self.children:
|
||||
## a parent blockdevice
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
_set_cached_value(cache_link, True)
|
||||
CACHE.set(cache_link, True)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -168,16 +210,16 @@ class Blockdevice:
|
|||
"""
|
||||
## check the cache first
|
||||
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:
|
||||
return cached
|
||||
|
||||
## is one of the devnodes of the device a physical volume?
|
||||
for one_lvm_pv in find_lvm_pv():
|
||||
if one_lvm_pv in self.devnodes:
|
||||
_set_cached_value(cache_link, True)
|
||||
CACHE.set(cache_link, True)
|
||||
return True
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
|
||||
|
||||
|
@ -186,23 +228,23 @@ class Blockdevice:
|
|||
"""
|
||||
## check the cache first
|
||||
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:
|
||||
return cached
|
||||
|
||||
## is one of the devnodes of the device a physical volume?
|
||||
## logical LVM volumes always depend on their physical volumes
|
||||
if not self.slaves:
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
## is one of the LVM physical volumes a device node of our slave(s)?
|
||||
for one_lvm_pv in find_lvm_pv():
|
||||
for one_slave in self.slaves:
|
||||
if one_lvm_pv in get_blockdevice(one_slave,
|
||||
self.sysblock_dir, self.devnode_dir).devnodes:
|
||||
_set_cached_value(cache_link, True)
|
||||
CACHE.set(cache_link, True)
|
||||
return True
|
||||
_set_cached_value(cache_link, False)
|
||||
CACHE.set(cache_link, False)
|
||||
return False
|
||||
|
||||
|
||||
|
@ -211,7 +253,7 @@ class Blockdevice:
|
|||
"""
|
||||
## check the cache first
|
||||
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:
|
||||
return cached
|
||||
|
||||
|
@ -229,7 +271,7 @@ class Blockdevice:
|
|||
result = False
|
||||
|
||||
## store result and return
|
||||
_set_cached_value(cache_link, result)
|
||||
CACHE.set(cache_link, result)
|
||||
return result
|
||||
|
||||
|
||||
|
@ -238,7 +280,7 @@ class Blockdevice:
|
|||
"""
|
||||
## check the cache first
|
||||
cache_link = ["blockdevice_info", self.name, "is_luks"]
|
||||
cached = _get_cached_value(cache_link)
|
||||
cached = CACHE.get(cache_link)
|
||||
if not cached is None:
|
||||
return cached
|
||||
|
||||
|
@ -262,7 +304,33 @@ class Blockdevice:
|
|||
proc.wait()
|
||||
result = proc.returncode == 0
|
||||
## 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
|
||||
|
||||
|
||||
|
@ -275,12 +343,23 @@ class Blockdevice:
|
|||
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):
|
||||
"""return the size (in kB) of the blockdevice
|
||||
"""return the size (in MB) of the blockdevice
|
||||
"""
|
||||
default = 0
|
||||
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:
|
||||
return default
|
||||
except ValueError:
|
||||
|
@ -358,6 +437,150 @@ class Blockdevice:
|
|||
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):
|
||||
"""display the name of the device
|
||||
"""
|
||||
|
@ -370,6 +593,8 @@ class Blockdevice:
|
|||
output = "%s:\n" % self.name
|
||||
output += "\t%s:\t%s\n" % ("blockdir", self.devdir)
|
||||
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" % ("size", self.size)
|
||||
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" % ("device nodes", self.devnodes)
|
||||
output += "\tflags:\t\t"
|
||||
for funcname in [ "storage", "md_raid", "partitionable", "luks",
|
||||
"lvm_pv", "lvm_lv"]:
|
||||
if getattr(self, "is_%s" % funcname)():
|
||||
output += "%s " % funcname
|
||||
for funcname in [ func for func in dir(self)
|
||||
if func.startswith("is_") and callable(getattr(self, func))]:
|
||||
if getattr(self, funcname)():
|
||||
output += "%s " % funcname[3:]
|
||||
output += "\n"
|
||||
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,
|
||||
sysblock_dir=DEFAULT_SYSBLOCK_DIR,
|
||||
devnode_dir=DEFAULT_DEVNODE_DIR):
|
||||
|
@ -401,16 +710,18 @@ def get_blockdevice(dev,
|
|||
else:
|
||||
return None
|
||||
devname = os.path.basename(devdir)
|
||||
dev = _get_cached_value(["blockdevices", devname])
|
||||
cache_link = ["blockdevices", devname]
|
||||
dev = CACHE.get(cache_link)
|
||||
if dev is None:
|
||||
dev = Blockdevice(devdir, sysblock_dir, devnode_dir)
|
||||
_set_cached_value(["blockdevices", devname], dev)
|
||||
CACHE.set(cache_link, dev)
|
||||
return dev
|
||||
|
||||
|
||||
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:
|
||||
return cached[:]
|
||||
|
||||
|
@ -431,98 +742,44 @@ def find_blockdevices(top_dir):
|
|||
fnames.remove(fname)
|
||||
|
||||
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[:]
|
||||
|
||||
|
||||
def find_lvm_pv():
|
||||
"""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:
|
||||
return cached[:]
|
||||
|
||||
#TODO: should we check, if LVM is supported at all?
|
||||
# e.g. by checking the existence of pvdisplay?
|
||||
prefs = _load_preferences()
|
||||
result = None
|
||||
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:
|
||||
# TODO: add a logging warning
|
||||
LOGGER.info("Failed to call 'pvdisplay' via 'super': %s" % err_msg)
|
||||
result = []
|
||||
if proc.returncode != 0:
|
||||
# TODO: add a logging warning
|
||||
LOGGER.info("Execution of 'pvdisplay' failed: %s" % \
|
||||
proc.stderr.read().strip())
|
||||
result = []
|
||||
if result is None:
|
||||
result = []
|
||||
for line in proc.stdout.readlines():
|
||||
result.append(line.split(":", 1)[0].strip())
|
||||
_set_cached_value(["lvm", "pv"], result)
|
||||
CACHE.set(cache_link, 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():
|
||||
prefs = cryptobox.core.settings.get_current_settings()
|
||||
if not prefs is None:
|
||||
|
@ -540,6 +797,10 @@ def _load_preferences():
|
|||
|
||||
|
||||
|
||||
## initialize cache
|
||||
CACHE = BlockdeviceCache()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
## list the properties of all available devices
|
||||
## this is just for testing purposes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright 2006 sense.lab e.V.
|
||||
# Copyright 02006-02007 sense.lab e.V.
|
||||
#
|
||||
# This file is part of the CryptoBox.
|
||||
#
|
||||
|
@ -55,6 +55,10 @@ class CryptoBoxContainer:
|
|||
|
||||
|
||||
def __init__(self, device, cbox):
|
||||
"""initialize the container
|
||||
|
||||
"device" is a cryptobox.core.blockdevice object
|
||||
"""
|
||||
self.device = device
|
||||
self.cbox = cbox
|
||||
self.uuid = None
|
||||
|
@ -149,7 +153,7 @@ class CryptoBoxContainer:
|
|||
e.g.: /dev/hdc1
|
||||
Available since: 0.3.0
|
||||
"""
|
||||
return self.device
|
||||
return self.device.devnodes[0]
|
||||
|
||||
|
||||
def get_type(self):
|
||||
|
@ -198,8 +202,7 @@ class CryptoBoxContainer:
|
|||
an error is indicated by "-1"
|
||||
Available since: 0.3.0
|
||||
"""
|
||||
import cryptobox.core.tools as cbxtools
|
||||
return cbxtools.get_blockdevice_size(self.device)
|
||||
return self.device.size
|
||||
|
||||
|
||||
def reset_object(self):
|
||||
|
@ -208,6 +211,7 @@ class CryptoBoxContainer:
|
|||
this is especially useful after changing the type via 'create'
|
||||
Available since: 0.3.0
|
||||
"""
|
||||
self.device.reset()
|
||||
self.uuid = self.__get_uuid()
|
||||
self.cont_type = self.__get_type_of_partition()
|
||||
self.fs_type = self.__get_fs_type()
|
||||
|
@ -277,7 +281,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
||||
"program", "cryptsetup",
|
||||
"luksAddKey",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
"--batch-mode"])
|
||||
proc.stdin.write("%s\n%s" % (oldpw, newpw))
|
||||
(output, errout) = proc.communicate()
|
||||
|
@ -302,7 +306,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["cryptsetup"],
|
||||
"--batch-mode",
|
||||
"luksDelKey",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
"%d" % (keyslot, )])
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
|
@ -318,7 +322,7 @@ class CryptoBoxContainer:
|
|||
while it is being formatted or similar.
|
||||
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):
|
||||
|
@ -328,7 +332,7 @@ class CryptoBoxContainer:
|
|||
The timeout is optional and defaults to five minutes.
|
||||
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 *********************
|
||||
|
@ -358,56 +362,13 @@ class CryptoBoxContainer:
|
|||
def __get_uuid(self):
|
||||
"""Retrieve the uuid of the container device.
|
||||
"""
|
||||
if self.__get_type_of_partition() == CONTAINERTYPES["luks"]:
|
||||
guess = self.__get_luks_uuid()
|
||||
else:
|
||||
guess = self.__get_non_luks_uuid()
|
||||
guess = self.device.uuid
|
||||
## did we get a valid value?
|
||||
if guess:
|
||||
return guess
|
||||
else:
|
||||
## emergency default value
|
||||
return self.device.replace(os.path.sep, "_")
|
||||
|
||||
|
||||
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()
|
||||
return self.device.name
|
||||
|
||||
|
||||
def __get_type_of_partition(self):
|
||||
|
@ -415,7 +376,7 @@ class CryptoBoxContainer:
|
|||
|
||||
see cryptobox.core.container.CONTAINERTYPES
|
||||
"""
|
||||
if self.__is_luks_partition():
|
||||
if self.device.is_luks():
|
||||
return CONTAINERTYPES["luks"]
|
||||
type_of_partition = self.__get_type_id_of_partition()
|
||||
if type_of_partition in FSTYPES["plain"]:
|
||||
|
@ -436,7 +397,7 @@ class CryptoBoxContainer:
|
|||
"-o", "value",
|
||||
"-c", os.devnull,
|
||||
"-w", os.devnull,
|
||||
self.device ])
|
||||
self.device.devnodes[0] ])
|
||||
(stdout, stderr) = proc.communicate()
|
||||
if proc.returncode == 0:
|
||||
## we found a uuid
|
||||
|
@ -454,13 +415,14 @@ class CryptoBoxContainer:
|
|||
def __get_fs_type(self):
|
||||
"returns the filesystem used on a container"
|
||||
## 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)
|
||||
## can't determine fs while encrypted
|
||||
if not self.is_mounted():
|
||||
return "unavailable"
|
||||
else:
|
||||
container = self.device
|
||||
container = self.device.devnodes[0]
|
||||
proc = subprocess.Popen(
|
||||
shell = False,
|
||||
stdout = subprocess.PIPE,
|
||||
|
@ -479,22 +441,6 @@ class CryptoBoxContainer:
|
|||
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):
|
||||
"return the name of the mountpoint of this volume"
|
||||
return os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], self.name)
|
||||
|
@ -525,7 +471,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
||||
"program", "cryptsetup",
|
||||
"luksOpen",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
self.name,
|
||||
"--batch-mode"])
|
||||
proc.stdin.write(password)
|
||||
|
@ -621,7 +567,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["super"],
|
||||
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
||||
"program", "mount",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
self.__get_mount_point()])
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
|
@ -691,7 +637,7 @@ class CryptoBoxContainer:
|
|||
args = [
|
||||
self.cbox.prefs["Programs"]["nice"],
|
||||
self.cbox.prefs["Programs"]["mkfs"],
|
||||
"-t", fs_type, self.device])
|
||||
"-t", fs_type, self.device.devnodes[0]])
|
||||
loc_data.proc.wait()
|
||||
## wait to allow error detection
|
||||
if loc_data.proc.returncode == 0:
|
||||
|
@ -711,7 +657,7 @@ class CryptoBoxContainer:
|
|||
time.sleep(3)
|
||||
## if the thread exited very fast, then it failed
|
||||
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")
|
||||
|
||||
|
||||
|
@ -736,7 +682,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
||||
"program", "cryptsetup",
|
||||
"luksFormat",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
"--batch-mode",
|
||||
"--cipher", self.cbox.prefs["Main"]["DefaultCipher"],
|
||||
"--iter-time", "2000"])
|
||||
|
@ -757,7 +703,7 @@ class CryptoBoxContainer:
|
|||
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
||||
"program", "cryptsetup",
|
||||
"luksOpen",
|
||||
self.device,
|
||||
self.device.devnodes[0],
|
||||
self.name,
|
||||
"--batch-mode"])
|
||||
proc.stdin.write(password)
|
||||
|
@ -808,7 +754,7 @@ class CryptoBoxContainer:
|
|||
time.sleep(3)
|
||||
## if the thread exited very fast, then it failed
|
||||
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")
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright 2006 sense.lab e.V.
|
||||
# Copyright 02006-02007 sense.lab e.V.
|
||||
#
|
||||
# This file is part of the CryptoBox.
|
||||
#
|
||||
|
@ -27,9 +27,8 @@ __revision__ = "$Id$"
|
|||
import sys
|
||||
import cryptobox.core.container as cbxContainer
|
||||
from cryptobox.core.exceptions import CBEnvironmentError, CBConfigUndefinedError
|
||||
import re
|
||||
import cryptobox.core.blockdevice as blockdevice
|
||||
import os
|
||||
import cryptobox.core.tools as cbxTools
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
|
@ -154,7 +153,8 @@ class CryptoBox:
|
|||
"""
|
||||
self.log.debug("rereading container list")
|
||||
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):
|
||||
self.__containers.append(cbxContainer.CryptoBoxContainer(device, self))
|
||||
## sort by container name
|
||||
|
@ -204,49 +204,30 @@ class CryptoBox:
|
|||
|
||||
The check is done by comparing the label of the filesystem with a string.
|
||||
"""
|
||||
proc = subprocess.Popen(
|
||||
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"]
|
||||
return device.label == 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
|
||||
|
||||
also check, if the device is readable and writeable for the current user
|
||||
"""
|
||||
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"]
|
||||
if type(allowed) == types.StringType:
|
||||
allowed = [allowed]
|
||||
for a_dev in allowed:
|
||||
if not a_dev:
|
||||
continue
|
||||
## double dots are not allowed (e.g. /dev/ide/../sda)
|
||||
if re.search("/\.\./", devicename):
|
||||
continue
|
||||
## it is not possible to check for 'realpath' - that does not work
|
||||
## for the cryptobox as /dev/ is bind-mounted (real hda-name is /opt/...)
|
||||
if re.search('^%s' % a_dev, devicename):
|
||||
self.log.debug("Adding valid device: %s" % devicename)
|
||||
return True
|
||||
self.log.debug("Skipping device not listed in Main->AllowedDevices: %s" \
|
||||
% devicename)
|
||||
for devnode in device.devnodes:
|
||||
if [ a_dev for a_dev in allowed if devnode.startswith(a_dev) ]:
|
||||
if os.access(devnode, os.R_OK | os.W_OK):
|
||||
self.log.debug("Adding valid device: %s" % devnode)
|
||||
## move the device to the first position
|
||||
device.devnodes.remove(devnode)
|
||||
device.devnodes.insert(0, devnode)
|
||||
return True
|
||||
else:
|
||||
self.log.debug("Skipping device without read and write" \
|
||||
+ "permissions: %s" % device.name)
|
||||
self.log.debug("Skipping unusable device: %s" % device.name)
|
||||
return False
|
||||
|
||||
|
||||
|
@ -269,7 +250,7 @@ class CryptoBox:
|
|||
|
||||
def get_container(self, 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:
|
||||
return all[0]
|
||||
else:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue