Browse Source

* fixed some minor problems caused by revision [1162]

* try to refresh the block device cache once if a device was requested, that is currently not in the list (after a minimum cache age passed)
* added an implicit sorting function for block devices (based on major/minor values)
* try all found device nodes, in case the first does not exist anymore (e.g. temporary device nodes)
master
lars 13 years ago
parent
commit
7b0de22e3a
  1. 119
      src/cryptobox/core/blockdevice.py
  2. 58
      src/cryptobox/core/container.py

119
src/cryptobox/core/blockdevice.py

@ -53,6 +53,7 @@ MAJOR_DEVNUM_MD_RAID = 9
## cache settings
CACHE_ENABLED = True
CACHE_EXPIRE_SECONDS = 120
CACHE_MINIMUM_AGE_FOR_REBUILD = 3
CACHE_MONITOR_FILE = '/proc/partitions'
## useful for manual profiling
@ -121,7 +122,12 @@ class Blockdevice:
"""
self.devnode_dir = devnode_dir
self.sysblock_dir = sysblock_dir
self.major, self.minor = major_minor
try:
self.major, self.minor = major_minor
except ValueError:
# invalid device given
raise cryptobox.core.exceptions.CBInternalError(
"invalid block device requested: %s" % str(major_minor))
# find the devdir (usually in /sys/block/)
for devdir, one_major_minor in find_blockdevices(self.sysblock_dir).items():
if major_minor == one_major_minor:
@ -147,6 +153,16 @@ class Blockdevice:
self.reset(empty_cache=False)
def __cmp__(self, other):
if (self.major < other.major) or \
((self.major == other.major) and (self.minor < other.minor)):
return -1
elif (self.major == other.major) and (self.minor == other.minor):
return 0
else:
return 1
def reset(self, empty_cache=True):
"""reread the data of the device
@ -171,6 +187,23 @@ class Blockdevice:
self.uuid = attributes["uuid"]
def get_device(self):
"""Returns the path of a device node representing this device
e.g.: /dev/hdc1
"""
# we need to check, which listed device nodes exists
# This is necessary, since temporary device nodes seem to be created
# immediately after partitioning a disk (e.g. "/dev/.tmp-22-1").
for dev in self.devnodes:
if os.path.exists(dev):
return dev
# none of the device nodes exists
self.cbox.log.warn("No valid device node found for %s out of %s" % \
(self.name, str(self.devnodes)))
return None
def is_valid(self):
"""check if the device is usable and valid
@ -343,7 +376,7 @@ class Blockdevice:
prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup",
"isLuks", self.devnodes[0]])
"isLuks", self.get_device()])
proc.wait()
result = proc.returncode == 0
## store result and return
@ -495,7 +528,7 @@ class Blockdevice:
prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup",
"luksUUID", self.devnodes[0] ])
"luksUUID", self.get_device()])
(output, error) = proc.communicate()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' to determine UUID: %s" \
@ -503,7 +536,7 @@ class Blockdevice:
return None
if proc.returncode != 0:
LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \
(prefs["Programs"]["cryptsetup"], self.devnodes[0],
(prefs["Programs"]["cryptsetup"], self.get_device(),
error))
return None
result = output.strip()
@ -557,8 +590,6 @@ class Blockdevice:
result = {"label": None, "type_id": None, "uuid": None}
if not self.is_valid():
return result
if self.is_luks():
return result
prefs = _load_preferences()
try:
proc = subprocess.Popen(
@ -571,12 +602,12 @@ class Blockdevice:
"-s", "UUID",
"-c", os.devnull,
"-w", os.devnull,
self.devnodes[0]])
self.get_device()])
(output, error) = proc.communicate()
except OSError, err_msg:
LOGGER.warning("Failed to call '%s' to determine label for " \
% prefs["Programs"]["blkid"] + "'%s': %s" % \
(self.devnodes[0], err_msg))
(self.get_device(), err_msg))
return result
if proc.returncode == 2:
## the device does not contain a filesystem (e.g. it is zeroed or
@ -584,7 +615,7 @@ class Blockdevice:
return result
if proc.returncode != 0:
LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \
(prefs["Programs"]["blkid"], self.devnodes[0],
(prefs["Programs"]["blkid"], self.get_device(),
error.strip()))
return result
# scan the output string for results
@ -592,7 +623,7 @@ class Blockdevice:
# /dev/hda1: TYPE="ext3" LABEL="neu"de"
pattern = {"LABEL": "label", "TYPE": "type_id", "UUID": "uuid"}
for name, attr in pattern.items():
match = re.search(r' %s="(.*?)"(?: [A-Z]+="|$)' % name, output)
match = re.search(r' %s="(.*?)"(?: [A-Z]+="| ?$)' % name, output)
if match:
result[attr] = match.groups()[0]
# check for special attributes of LUKS devices and LVM physical volumes
@ -625,6 +656,7 @@ class Blockdevice:
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\t%s\n" % ("label", self.label)
output += "\t%s:\t\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" % ("range", self.range)
output += "\t%s:\t\t%s\n" % ("size", self.size)
@ -726,7 +758,14 @@ class BlockdeviceCache:
ref = ref[element]
## store the item
ref[link[-1]] = item
def get_age(self):
age = CACHE_EXPIRE_SECONDS - (self.expires - int(time.time()))
if age < 0:
return 0
else:
return age
def __get_major_minor(dev):
@ -771,7 +810,7 @@ def __get_major_minor(dev):
def get_blockdevice(dev,
sysblock_dir=DEFAULT_SYSBLOCK_DIR,
devnode_dir=DEFAULT_DEVNODE_DIR):
devnode_dir=DEFAULT_DEVNODE_DIR, retry_once=True):
if isinstance(dev, Blockdevice):
# it is already a blockdevice
major_minor = (dev.major, dev.minor)
@ -788,13 +827,29 @@ def get_blockdevice(dev,
major_minor = one_major_minor
break
else:
return None
cache_link = ["blockdevices", major_minor]
dev = CACHE.get(cache_link)
if dev is None:
dev = Blockdevice(major_minor, sysblock_dir, devnode_dir)
CACHE.set(cache_link, dev)
return dev
# rebuild the cache if it is rather old and try again
# this is necessary for the "partition" plugin
if retry_once and (CACHE.get_age() > CACHE_MINIMUM_AGE_FOR_REBUILD):
CACHE.reset()
device = get_blockdevice(dev, sysblock_dir, devnode_dir, False)
if not device is None:
major_minor = (device.major, device.minor)
else:
major_minor = None
else:
# it seems like it does really not exist
major_minor = None
if major_minor:
cache_link = ["blockdevices", major_minor]
dev = CACHE.get(cache_link)
if dev is None:
dev = Blockdevice(major_minor, sysblock_dir, devnode_dir)
if not dev is None:
CACHE.set(cache_link, dev)
return dev
else:
return None
def find_blockdevices(top_dir):
@ -939,12 +994,13 @@ def _load_preferences():
CACHE = BlockdeviceCache()
def show_devices(blocks, show):
def show_devices(blocks):
result = ""
if len(blocks) > 0:
## show all devices and their properties
show("Properties of all devices:")
result += "Properties of all devices:\n"
for device in blocks:
show(device.info())
result += device.info() + "\n"
## discover all self-check methods
example = blocks[0]
@ -953,28 +1009,24 @@ def show_devices(blocks, show):
and method.startswith("is_")]
## list all checks and the respective devices
for check in flag_checker:
show("List of '%s' devices:" % check[3:])
result += "List of '%s' devices:" % check[3:] + "\n"
for device in blocks:
try:
if getattr(device, check)():
show("\t%s" % device)
result += "\t%s" % device + "\n"
except TypeError:
# ignore tests that need a second argument
pass
show()
result += "\n"
return result
def get_devices_and_show():
## list the properties of all available devices
## this is just for testing purposes
blocks = Blockdevices().get_devices()
blocks.sort(key=lambda x: x.name)
## do we want to show the result?
def show(text=""):
if IS_VISIBLE:
print text
show_devices(blocks, show)
return show_devices(blocks)
if __name__ == '__main__':
@ -983,12 +1035,11 @@ if __name__ == '__main__':
# show some profiling information (requires the python-profiler package)
import cProfile
import pstats
IS_VISIBLE = False
for index in range(3):
print "Run: %d" % index
cProfile.run('get_devices_and_show()', 'profinfo')
p = pstats.Stats('profinfo')
p.sort_stats('cumulative').print_stats(20)
else:
get_devices_and_show()
print get_devices_and_show()

58
src/cryptobox/core/container.py

@ -147,12 +147,12 @@ class CryptoBoxContainer:
def get_device(self):
"""Return the device name of the container
"""Returns the path of a device node representing this device
e.g.: /dev/hdc1
Available since: 0.3.0
"""
return self.device.devnodes[0]
return self.device.get_device()
def get_type(self):
@ -467,7 +467,7 @@ class CryptoBoxContainer:
if self.device.holders:
## the decrypted blockdevice is available
plain_device = cryptobox.core.blockdevice.get_blockdevice(
self.device.holders[0]).devnodes[0]
self.device.holders[0]).get_device()
else:
err_msg = "Could not find the plaintext container for " \
+ "'%s': %s" % (self.get_device(), "no hold devices found")
@ -616,6 +616,11 @@ class CryptoBoxContainer:
if self.is_mounted():
raise CBVolumeIsActive(
"deactivate the partition before filesystem initialization")
if self.get_device() is None:
raise CBCreateError("No valid device for (%s) found: %s" % \
(self.device.devdir, self.device.devnodes))
# useful for debugging: disable threading
ENABLE_THREADING = True
def format():
"""This function will get called as a seperate thread.
@ -628,8 +633,14 @@ class CryptoBoxContainer:
loc_data.old_name = self.get_name()
self.set_busy(True, 600)
## give the main thread a chance to continue
loc_data.child_pid = os.fork()
if loc_data.child_pid == 0:
if ENABLE_THREADING:
loc_data.child_pid = os.fork()
primary_thread_enabled = loc_data.child_pid == 0
secondary_thread_enabled = not primary_thread_enabled
else:
primary_thread_enabled = True
secondary_thread_enabled = True
if primary_thread_enabled:
loc_data.proc = subprocess.Popen(
shell = False,
stdin = None,
@ -640,26 +651,35 @@ class CryptoBoxContainer:
self.cbox.prefs["Programs"]["mkfs"],
"-t", fs_type, self.get_device()])
loc_data.proc.wait()
## wait to allow error detection
if loc_data.proc.returncode == 0:
time.sleep(5)
## skip cleanup stuff (as common for sys.exit)
os._exit(0)
else:
os.waitpid(loc_data.child_pid, 0)
if ENABLE_THREADING:
## wait to allow error detection
if loc_data.proc.returncode == 0:
time.sleep(5)
## skip cleanup stuff (as common for sys.exit)
os._exit(0)
if secondary_thread_enabled:
if ENABLE_THREADING:
os.waitpid(loc_data.child_pid, 0)
try:
self.set_name(loc_data.old_name)
except CBNameIsInUse:
pass
self.set_busy(False)
bg_task = threading.Thread(target=format)
bg_task.setDaemon(True)
bg_task.start()
time.sleep(3)
## if the thread exited very fast, then it failed
if not bg_task.isAlive():
return (loc_data.proc.returncode == 0)
if ENABLE_THREADING:
bg_task = threading.Thread(target=format)
bg_task.setDaemon(True)
bg_task.start()
time.sleep(3)
## if the thread exited very fast, then it failed
success = bg_task.isAlive()
else:
success = format()
if not success:
raise CBCreateError("formatting of device (%s) failed out " % \
self.get_device() + "of unknown reasons")
else:
return True
def __create_luks(self, password, fs_type="ext3"):
@ -718,7 +738,7 @@ class CryptoBoxContainer:
if self.device.holders:
## the decrypted blockdevice is available
plain_device = cryptobox.core.blockdevice.get_blockdevice(
self.device.holders[0]).devnodes[0]
self.device.holders[0]).get_device()
else:
err_msg = "Could not find the plaintext container for " \
+ "'%s': %s" % (self.get_device(), "no hold devices found")

Loading…
Cancel
Save