* adapt block device handling to linux kernel >v2.6.26 (symlinks in /sys/block/)

* turned all attributes of the "Blockdevice" class into properties (postponing evaluation)
* unify cache_links for blockdevice information
* add default location of config file to preferences handling
This commit is contained in:
lars 2009-06-16 01:58:29 +00:00
parent 4f24c813cd
commit d5dce0887a
1 changed files with 219 additions and 152 deletions

View File

@ -130,29 +130,7 @@ class Blockdevice:
# invalid device given # invalid device given
raise cryptobox.core.exceptions.CBInternalError( raise cryptobox.core.exceptions.CBInternalError(
"invalid block device requested: %s" % str(major_minor)) "invalid block device requested: %s" % str(major_minor))
# find the devdir (usually in /sys/block/) self.__cache_link = ["blockdevice_info", (self.major, self.minor)]
for devdir, one_major_minor in find_blockdevices(self.sysblock_dir).items():
if major_minor == one_major_minor:
self.devdir = devdir
break
else:
# we did not find a suitable device
raise cryptobox.core.exceptions.CBInternalError(
"could not find blockdevice with the given major/minor: " \
+ "%d/%d" % (self.major, self.minor))
self.name = os.path.basename(self.devdir)
## "reset" below will fill these values
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.type_id = None
self.label = None
self.reset(empty_cache=False)
def __cmp__(self, other): def __cmp__(self, other):
@ -165,7 +143,7 @@ class Blockdevice:
return 1 return 1
def reset(self, empty_cache=True): def reset(self):
"""reread the data of the device """reread the data of the device
usually we will have to reset the cache, too usually we will have to reset the cache, too
@ -174,27 +152,7 @@ class Blockdevice:
@type empty_cache: boolean @type empty_cache: boolean
@param empty_cache: Whether to discard the cached information or not. @param empty_cache: Whether to discard the cached information or not.
""" """
if empty_cache: CACHE.reset(self.__cache_link)
CACHE.reset(["blockdevice_info", (self.major, self.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()
if self.get_device() is None:
CACHE.reset(["blockdevice_nodes"])
self.devnodes = self.__get_device_nodes()
if self.get_device() is None:
LOGGER.warning("No suitable device nodes found for: %s" % \
self.devdir)
# the following code would fail without device node -> skip it
return
attributes = self.__get_blkid_attributes()
self.label = attributes["label"]
self.type_id = attributes["type_id"]
self.uuid = attributes["uuid"]
def get_device(self): def get_device(self):
@ -222,7 +180,8 @@ class Blockdevice:
@rtype: boolean @rtype: boolean
@return: 'True' for a valid blockdevice @return: 'True' for a valid blockdevice
""" """
if not self.devnodes: # return False if no device node for this block device exists
if self.get_device() is None:
return False return False
## check valid major_minor ## check valid major_minor
try: try:
@ -250,7 +209,7 @@ class Blockdevice:
@return: 'True' for a device usable as a storage @return: 'True' for a device usable as a storage
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_storage"] cache_link = self.__cache_link + ["is_storage"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -294,7 +253,7 @@ class Blockdevice:
"""return if the device is a physical volume of a LVM """return if the device is a physical volume of a LVM
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_lvm_pv"] cache_link = self.__cache_link + ["is_lvm_pv"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -312,7 +271,7 @@ class Blockdevice:
"""return if the device is a logical volume of a LVM """return if the device is a logical volume of a LVM
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_lvm_lv"] cache_link = self.__cache_link + ["is_lvm_lv"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -337,7 +296,7 @@ class Blockdevice:
"""check if the device is the base of a md raid device """check if the device is the base of a md raid device
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_md_raid"] cache_link = self.__cache_link + ["is_md_raid"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -364,7 +323,7 @@ class Blockdevice:
"""check if the device is a luks container """check if the device is a luks container
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_luks"] cache_link = self.__cache_link + ["is_luks"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -400,7 +359,7 @@ class Blockdevice:
"""check if the device is marked as 'removable' """check if the device is marked as 'removable'
""" """
## check the cache first ## check the cache first
cache_link = ["blockdevice_info", (self.major, self.minor), "is_removable"] cache_link = self.__cache_link + ["is_removable"]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached return cached
@ -443,7 +402,8 @@ class Blockdevice:
"invalid arguments for 'is_parent_of'") "invalid arguments for 'is_parent_of'")
## recursively go through all the children ## recursively go through all the children
for child_devname in self.children: for child_devname in self.children:
child_dev = get_blockdevice(child_devname) child_dev = get_blockdevice(child_devname, self.sysblock_dir,
self.devnode_dir)
## direct child? ## direct child?
if child_dev == ask_child: if child_dev == ask_child:
return True return True
@ -454,6 +414,28 @@ class Blockdevice:
return False return False
def __get_devdir(self):
cache_link = self.__cache_link + ["devdir"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
result = None
# find the devdir (usually in /sys/block/)
for devdir, one_major_minor in find_blockdevices(self.sysblock_dir).items():
if (self.major, self.minor) == one_major_minor:
result = devdir
break
else:
# we did not find a suitable device
raise cryptobox.core.exceptions.CBInternalError(
"could not find blockdevice with the given major/minor: " \
+ "%d/%d" % (self.major, self.minor))
CACHE.set(cache_link, result)
return result
def __get_dev_related(self, subdir): def __get_dev_related(self, subdir):
"""return the content of sub directories (e.g. 'holders' or 'slaves') """return the content of sub directories (e.g. 'holders' or 'slaves')
""" """
@ -475,15 +457,20 @@ class Blockdevice:
def __get_size(self): def __get_size(self):
"""return the size (in MB) of the blockdevice """return the size (in MB) of the blockdevice
""" """
default = 0 cache_link = self.__cache_link + ["size"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
try: try:
size_blocks = int(file(os.path.join(self.devdir, 'size')).read()) size_blocks = int(file(os.path.join(self.devdir, 'size')).read())
## size is defined as the number of blocks (512 byte each) ## size is defined as the number of blocks (512 byte each)
return int(size_blocks*512/1024/1024) result = int(size_blocks*512/1024/1024)
except OSError: except (IOError, ValueError):
return default result = 0
except ValueError:
return default CACHE.set(cache_link, result)
return result
def __get_device_range(self): def __get_device_range(self):
@ -491,15 +478,18 @@ class Blockdevice:
partitionable blockdevices have a range > 1 partitionable blockdevices have a range > 1
""" """
default = 1 cache_link = self.__cache_link + ["device_range"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
try: try:
content = file(os.path.join(self.devdir, "range")).read() result = int(file(os.path.join(self.devdir, "range")).read())
except IOError: except (IOError, ValueError):
return default result = 1
try:
return int(content) CACHE.set(cache_link, result)
except ValueError: return result
return default
def __get_children(self): def __get_children(self):
@ -507,29 +497,50 @@ class Blockdevice:
all holders, subdevices and children of subdevices all holders, subdevices and children of subdevices
""" """
cache_link = self.__cache_link + ["children"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
direct_children = [ direct_children = [
get_blockdevice(major_minor, self.sysblock_dir, self.devnode_dir).name get_blockdevice(major_minor, self.sysblock_dir, self.devnode_dir).name
for major_minor in find_blockdevices(self.devdir).values()] for major_minor in find_blockdevices(self.devdir,
follow_links=False).values()]
direct_children.extend(self.holders[:]) direct_children.extend(self.holders[:])
children = direct_children[:] children = direct_children[:]
for dchild in direct_children: for dchild in direct_children:
children.extend(get_blockdevice(dchild, self.sysblock_dir, children.extend(get_blockdevice(dchild, self.sysblock_dir,
self.devnode_dir).children) self.devnode_dir, follow_links=False).children)
CACHE.set(cache_link, children)
return children return children
def __get_device_nodes(self): def __get_device_nodes(self):
"""get all device nodes with the major/minor combination of the device """get all device nodes with the major/minor combination of the device
""" """
cache_link = self.__cache_link + ["device_nodes"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
try: try:
return find_device_nodes(self.devnode_dir)[(self.major, self.minor)] result = find_device_nodes(self.devnode_dir)[(self.major, self.minor)]
except KeyError: except KeyError:
return [] result = []
CACHE.set(cache_link, result)
return result
def __get_uuid_luks(self): def __get_uuid_luks(self):
"""determine the unique identifier of luks devices """determine the unique identifier of luks devices
""" """
cache_link = self.__cache_link + ["uuid_luks"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
prefs = _load_preferences() prefs = _load_preferences()
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
@ -552,15 +563,22 @@ class Blockdevice:
error)) error))
return None return None
result = output.strip() result = output.strip()
if result: if not result:
return result result = None
else:
return None CACHE.set(cache_link, result)
return result
def __get_uuid_lvm_pv(self): def __get_uuid_lvm_pv(self):
"""determine the unique identifier of physical LVM volumes """determine the unique identifier of physical LVM volumes
""" """
cache_link = self.__cache_link + ["uuid_lvm_pv"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
result = None
if not OPTIONAL_PROGS["lvm"]: if not OPTIONAL_PROGS["lvm"]:
## pvdisplay is not installed - skip it ## pvdisplay is not installed - skip it
return None return None
@ -589,9 +607,10 @@ class Blockdevice:
for line in output.splitlines(): for line in output.splitlines():
items = line.strip().split(":") items = line.strip().split(":")
if (len(items) == 12) and (items[0] in self.devnodes): if (len(items) == 12) and (items[0] in self.devnodes):
return items[11] result = items[11]
## not found
return None CACHE.set(cache_link, result)
return result
def __get_blkid_attributes(self): def __get_blkid_attributes(self):
@ -599,53 +618,64 @@ class Blockdevice:
returns a dictionary containing label, type_id and uuid returns a dictionary containing label, type_id and uuid
""" """
cache_link = self.__cache_link + ["blkid_attributes"]
cached = CACHE.get(cache_link)
if not cached is None:
return cached
result = {"label": None, "type_id": None, "uuid": None} result = {"label": None, "type_id": None, "uuid": None}
if not self.is_valid(): if self.is_valid():
return result prefs = _load_preferences()
prefs = _load_preferences() try:
try: proc = subprocess.Popen(
proc = subprocess.Popen( shell = False,
shell = False, stdout = subprocess.PIPE,
stdout = subprocess.PIPE, stderr = subprocess.PIPE,
stderr = subprocess.PIPE, args = [ prefs["Programs"]["blkid"],
args = [ prefs["Programs"]["blkid"], "-s", "LABEL",
"-s", "LABEL", "-s", "TYPE",
"-s", "TYPE", "-s", "UUID",
"-s", "UUID", "-c", os.devnull,
"-c", os.devnull, "-w", os.devnull,
"-w", os.devnull, self.get_device()])
self.get_device()]) (output, error) = proc.communicate()
(output, error) = proc.communicate() except OSError, err_msg:
except OSError, err_msg: LOGGER.warning("Failed to call '%s' to determine label for " \
LOGGER.warning("Failed to call '%s' to determine label for " \ % prefs["Programs"]["blkid"] + "'%s': %s" % \
% prefs["Programs"]["blkid"] + "'%s': %s" % \ (self.get_device(), err_msg))
(self.get_device(), err_msg)) else:
return result if proc.returncode == 2:
if proc.returncode == 2: ## the device does not contain a filesystem (e.g. it is zeroed or
## the device does not contain a filesystem (e.g. it is zeroed or ## it contains a partition table)
## it contains a partition table) pass
return result elif proc.returncode != 0:
if proc.returncode != 0: LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \
LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \ (prefs["Programs"]["blkid"], self.get_device(),
(prefs["Programs"]["blkid"], self.get_device(), error.strip()))
error.strip())) pass
return result else:
# scan the output string for results # scan the output string for results
# the output string could look like this: # the output string could look like this:
# /dev/hda1: TYPE="ext3" LABEL="neu"de" # /dev/hda1: TYPE="ext3" LABEL="neu"de"
pattern = {"LABEL": "label", "TYPE": "type_id", "UUID": "uuid"} pattern = {"LABEL": "label", "TYPE": "type_id", "UUID": "uuid"}
for name, attr in pattern.items(): for name, attr in pattern.items():
match = re.search(r' %s="(.*?)"(?: [A-Z]+="| ?$)' % name, output) match = re.search(r' %s="(.*?)"(?: [A-Z]+="| ?$)' % \
if match: name, output)
result[attr] = match.groups()[0] if match:
# check for special attributes of LUKS devices and LVM physical volumes result[attr] = match.groups()[0]
# In this case the previously retrieved "uuid" value is overwritten. # Check for special attributes of LUKS devices and LVM
## UUIDs of physical LVM volumes can only be determined via pvdisplay # physical volumes.
if self.is_lvm_pv(): # In this case the previously retrieved "uuid" value is
result["uuid"] = self.__get_uuid_lvm_pv() # overwritten.
## UUIDs of luks devices can be determined via luksDump # UUIDs of physical LVM volumes can only be determined via
elif self.is_luks(): # pvdisplay.
result["uuid"] = self.__get_uuid_luks() if self.is_lvm_pv():
result["uuid"] = self.__get_uuid_lvm_pv()
## UUIDs of luks devices can be determined via luksDump
elif self.is_luks():
result["uuid"] = self.__get_uuid_luks()
CACHE.set(cache_link, result)
return result return result
@ -668,7 +698,7 @@ class Blockdevice:
output += "\t%s:\t%s\n" % ("blockdir", self.devdir) output += "\t%s:\t%s\n" % ("blockdir", self.devdir)
output += "\t%s:\t%d/%d\n" % ("major/minor", self.major, self.minor) output += "\t%s:\t%d/%d\n" % ("major/minor", self.major, self.minor)
output += "\t%s:\t\t%s\n" % ("label", self.label) output += "\t%s:\t\t%s\n" % ("label", self.label)
output += "\t%s:\t\t%s\n" % ("type_id", self.type_id) output += "\t%s:\t%s\n" % ("type_id", self.type_id)
output += "\t%s:\t\t%s\n" % ("UUID", self.uuid) 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)
@ -689,6 +719,21 @@ class Blockdevice:
return output return output
devdir = property(__get_devdir)
name = property(lambda self: os.path.basename(self.devdir))
size = property(__get_size)
size_human = property(__get_size_human)
range = property(__get_device_range)
slaves = property(lambda self: self.__get_dev_related("slaves"))
holders = property(lambda self: self.__get_dev_related("holders"))
children = property(__get_children)
devnodes = property(__get_device_nodes)
label = property(lambda self: self.__get_blkid_attributes()["label"])
type_id = property(lambda self: self.__get_blkid_attributes()["type_id"])
uuid = property(lambda self: self.__get_blkid_attributes()["uuid"])
class BlockdeviceCache: class BlockdeviceCache:
"""manage cached results of blockdevce queries """manage cached results of blockdevce queries
@ -719,7 +764,7 @@ class BlockdeviceCache:
self.set(target, {}) self.set(target, {})
elif isinstance(target, Blockdevice): elif isinstance(target, Blockdevice):
# we do not reset the expire date # we do not reset the expire date
self.set(["blockdevice_info", (target.major, target.minor)], {}) target.reset()
else: else:
LOGGER.log.warn("Invalid argument type for reset: %s" % \ LOGGER.log.warn("Invalid argument type for reset: %s" % \
str(type(target))) str(type(target)))
@ -828,7 +873,7 @@ def __get_major_minor(dev):
def get_blockdevice(dev, def get_blockdevice(dev,
sysblock_dir=DEFAULT_SYSBLOCK_DIR, sysblock_dir=DEFAULT_SYSBLOCK_DIR,
devnode_dir=DEFAULT_DEVNODE_DIR, retry_once=True): devnode_dir=DEFAULT_DEVNODE_DIR, retry_once=True, follow_links=True):
if dev is None: if dev is None:
return None return None
elif isinstance(dev, Blockdevice): elif isinstance(dev, Blockdevice):
@ -842,7 +887,8 @@ def get_blockdevice(dev,
major_minor = __get_major_minor(dev) major_minor = __get_major_minor(dev)
else: else:
## the name of a blockdevice (e.g.: 'dm-0') ## the name of a blockdevice (e.g.: 'dm-0')
for one_devdir, one_major_minor in find_blockdevices(sysblock_dir).items(): for one_devdir, one_major_minor in find_blockdevices(sysblock_dir,
follow_links).items():
if os.path.basename(one_devdir) == dev: if os.path.basename(one_devdir) == dev:
major_minor = one_major_minor major_minor = one_major_minor
break break
@ -851,7 +897,8 @@ def get_blockdevice(dev,
# this is necessary for the "partition" plugin # this is necessary for the "partition" plugin
if retry_once and (CACHE.get_age() > CACHE_MINIMUM_AGE_FOR_REBUILD): if retry_once and (CACHE.get_age() > CACHE_MINIMUM_AGE_FOR_REBUILD):
CACHE.reset() CACHE.reset()
device = get_blockdevice(dev, sysblock_dir, devnode_dir, False) device = get_blockdevice(dev, sysblock_dir, devnode_dir,
retry_once=False, follow_links=follow_links)
if not device is None: if not device is None:
major_minor = (device.major, device.minor) major_minor = (device.major, device.minor)
else: else:
@ -872,33 +919,43 @@ def get_blockdevice(dev,
def find_blockdevices(top_dir): def find_blockdevices(top_dir, follow_links=True):
# normalize the input directory
top_dir = os.path.realpath(top_dir)
cache_link = ["blockdevice_dirs", top_dir] cache_link = ["blockdevice_dirs", top_dir]
cached = CACHE.get(cache_link) cached = CACHE.get(cache_link)
if not cached is None: if not cached is None:
return cached.copy() return cached.copy()
dev_dirs = {} dev_dirs = {}
dev_file_name = 'dev'
walk_dirs = [top_dir]
walk_dir_index = 0
def look4dev_dirs(arg, dirname, fnames): while walk_dir_index < len(walk_dirs):
## ignore the top level directory to avoid infinite recursion for for dirname, dirs, fnames in os.walk(walk_dirs[walk_dir_index]):
## get_children ## add directories containing the file 'dev' to the list
if os.path.samefile(dirname, top_dir): dev_file_path = os.path.join(dirname, dev_file_name)
return # don't include the top-level device itself
## add directories containing the file 'dev' to the list if (os.path.realpath(dirname) != top_dir) and \
dev_file_path = os.path.join(dirname, arg) (dev_file_name in fnames) and os.path.isfile(dev_file_path):
if (arg in fnames) and os.path.isfile(dev_file_path): major_minor = __get_major_minor(dirname)
major_minor = __get_major_minor(dirname) if (not major_minor is None) and \
if not major_minor is None: (not major_minor in dev_dirs.values()):
dev_dirs[dirname] = major_minor dev_dirs[dirname] = major_minor
for fname in fnames: # follow symlinks
## remove symlinks and non-directories if follow_links and (walk_dir_index == 0):
fullname = os.path.join(dirname, fname) for dname in dirs:
if os.path.islink(fullname) or (not os.path.isdir(fullname)): fullpath = os.path.join(dirname, dname)
fnames.remove(fname) if (os.path.islink(fullpath) and os.path.isdir(fullpath)):
fullpath = os.path.realpath(fullpath)
if not fullpath in walk_dirs:
if not [one_walk for one_walk in walk_dirs
if os.path.samefile(one_walk, fullpath)]:
walk_dirs.append(fullpath)
walk_dir_index += 1
os.path.walk(top_dir, look4dev_dirs, 'dev')
CACHE.set(cache_link, dev_dirs) CACHE.set(cache_link, dev_dirs)
return dev_dirs.copy() return dev_dirs.copy()
@ -981,13 +1038,23 @@ def _load_preferences():
return prefs return prefs
## we have to load an emergency fallback for proper function ## we have to load an emergency fallback for proper function
## this is mainly useful for local testing ## this is mainly useful for local testing
alternative_locations = []
# try to use the '/bin/cryptobox.conf' location within a local working
# copy of the subversion repository
root_dir = os.path.realpath(os.path.join(globals()["cryptobox"].__path__[0], root_dir = os.path.realpath(os.path.join(globals()["cryptobox"].__path__[0],
os.path.pardir, os.path.pardir)) os.path.pardir, os.path.pardir))
config_file = os.path.join(root_dir, "bin", "cryptobox.conf") alternative_locations.append(
## we have to chdir to the 'bin' directory - otherwise the paths in os.path.join(root_dir, "bin", "cryptobox.conf"))
## cryptobox.conf do not work # try the default config file location
os.chdir(os.path.dirname(config_file)) alternative_locations.append('/etc/cryptobox-server/cryptobox.conf')
return cryptobox.core.settings.CryptoBoxSettings(config_file) for config_file in alternative_locations:
if os.path.exists(config_file):
# we have to chdir to the 'bin' directory - otherwise the paths in
# cryptobox.conf do not work
os.chdir(os.path.dirname(config_file))
return cryptobox.core.settings.CryptoBoxSettings(config_file)
else:
raise CBConfigUnavailableError()