* 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)
This commit is contained in:
lars 2009-06-11 09:30:17 +00:00
parent 22021866b4
commit 7b0de22e3a
2 changed files with 124 additions and 53 deletions

View File

@ -53,6 +53,7 @@ MAJOR_DEVNUM_MD_RAID = 9
## cache settings ## cache settings
CACHE_ENABLED = True CACHE_ENABLED = True
CACHE_EXPIRE_SECONDS = 120 CACHE_EXPIRE_SECONDS = 120
CACHE_MINIMUM_AGE_FOR_REBUILD = 3
CACHE_MONITOR_FILE = '/proc/partitions' CACHE_MONITOR_FILE = '/proc/partitions'
## useful for manual profiling ## useful for manual profiling
@ -121,7 +122,12 @@ class Blockdevice:
""" """
self.devnode_dir = devnode_dir self.devnode_dir = devnode_dir
self.sysblock_dir = sysblock_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/) # find the devdir (usually in /sys/block/)
for devdir, one_major_minor in find_blockdevices(self.sysblock_dir).items(): for devdir, one_major_minor in find_blockdevices(self.sysblock_dir).items():
if major_minor == one_major_minor: if major_minor == one_major_minor:
@ -147,6 +153,16 @@ class Blockdevice:
self.reset(empty_cache=False) 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): def reset(self, empty_cache=True):
"""reread the data of the device """reread the data of the device
@ -171,6 +187,23 @@ class Blockdevice:
self.uuid = attributes["uuid"] 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): def is_valid(self):
"""check if the device is usable and valid """check if the device is usable and valid
@ -343,7 +376,7 @@ class Blockdevice:
prefs["Programs"]["super"], prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"], prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"isLuks", self.devnodes[0]]) "isLuks", self.get_device()])
proc.wait() proc.wait()
result = proc.returncode == 0 result = proc.returncode == 0
## store result and return ## store result and return
@ -495,7 +528,7 @@ class Blockdevice:
prefs["Programs"]["super"], prefs["Programs"]["super"],
prefs["Programs"]["CryptoBoxRootActions"], prefs["Programs"]["CryptoBoxRootActions"],
"program", "cryptsetup", "program", "cryptsetup",
"luksUUID", self.devnodes[0] ]) "luksUUID", 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 UUID: %s" \ LOGGER.warning("Failed to call '%s' to determine UUID: %s" \
@ -503,7 +536,7 @@ class Blockdevice:
return None return None
if 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"]["cryptsetup"], self.devnodes[0], (prefs["Programs"]["cryptsetup"], self.get_device(),
error)) error))
return None return None
result = output.strip() result = output.strip()
@ -557,8 +590,6 @@ class Blockdevice:
result = {"label": None, "type_id": None, "uuid": None} result = {"label": None, "type_id": None, "uuid": None}
if not self.is_valid(): if not self.is_valid():
return result return result
if self.is_luks():
return result
prefs = _load_preferences() prefs = _load_preferences()
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
@ -571,12 +602,12 @@ class Blockdevice:
"-s", "UUID", "-s", "UUID",
"-c", os.devnull, "-c", os.devnull,
"-w", os.devnull, "-w", os.devnull,
self.devnodes[0]]) 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.devnodes[0], err_msg)) (self.get_device(), err_msg))
return result 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
@ -584,7 +615,7 @@ class Blockdevice:
return result return result
if 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.devnodes[0], (prefs["Programs"]["blkid"], self.get_device(),
error.strip())) error.strip()))
return result return result
# scan the output string for results # scan the output string for results
@ -592,7 +623,7 @@ class Blockdevice:
# /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]+="| ?$)' % name, output)
if match: if match:
result[attr] = match.groups()[0] result[attr] = match.groups()[0]
# check for special attributes of LUKS devices and LVM physical volumes # 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%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\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)
@ -726,7 +758,14 @@ class BlockdeviceCache:
ref = ref[element] ref = ref[element]
## store the item ## store the item
ref[link[-1]] = 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): def __get_major_minor(dev):
@ -771,7 +810,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): devnode_dir=DEFAULT_DEVNODE_DIR, retry_once=True):
if isinstance(dev, Blockdevice): if isinstance(dev, Blockdevice):
# it is already a blockdevice # it is already a blockdevice
major_minor = (dev.major, dev.minor) major_minor = (dev.major, dev.minor)
@ -788,13 +827,29 @@ def get_blockdevice(dev,
major_minor = one_major_minor major_minor = one_major_minor
break break
else: else:
return None # rebuild the cache if it is rather old and try again
cache_link = ["blockdevices", major_minor] # this is necessary for the "partition" plugin
dev = CACHE.get(cache_link) if retry_once and (CACHE.get_age() > CACHE_MINIMUM_AGE_FOR_REBUILD):
if dev is None: CACHE.reset()
dev = Blockdevice(major_minor, sysblock_dir, devnode_dir) device = get_blockdevice(dev, sysblock_dir, devnode_dir, False)
CACHE.set(cache_link, dev) if not device is None:
return dev 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): def find_blockdevices(top_dir):
@ -939,12 +994,13 @@ def _load_preferences():
CACHE = BlockdeviceCache() CACHE = BlockdeviceCache()
def show_devices(blocks, show): def show_devices(blocks):
result = ""
if len(blocks) > 0: if len(blocks) > 0:
## show all devices and their properties ## show all devices and their properties
show("Properties of all devices:") result += "Properties of all devices:\n"
for device in blocks: for device in blocks:
show(device.info()) result += device.info() + "\n"
## discover all self-check methods ## discover all self-check methods
example = blocks[0] example = blocks[0]
@ -953,28 +1009,24 @@ def show_devices(blocks, show):
and method.startswith("is_")] and method.startswith("is_")]
## list all checks and the respective devices ## list all checks and the respective devices
for check in flag_checker: for check in flag_checker:
show("List of '%s' devices:" % check[3:]) result += "List of '%s' devices:" % check[3:] + "\n"
for device in blocks: for device in blocks:
try: try:
if getattr(device, check)(): if getattr(device, check)():
show("\t%s" % device) result += "\t%s" % device + "\n"
except TypeError: except TypeError:
# ignore tests that need a second argument # ignore tests that need a second argument
pass pass
show() result += "\n"
return result
def get_devices_and_show(): def get_devices_and_show():
## 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
blocks = Blockdevices().get_devices() blocks = Blockdevices().get_devices()
blocks.sort(key=lambda x: x.name) blocks.sort(key=lambda x: x.name)
return show_devices(blocks)
## do we want to show the result?
def show(text=""):
if IS_VISIBLE:
print text
show_devices(blocks, show)
if __name__ == '__main__': if __name__ == '__main__':
@ -983,12 +1035,11 @@ if __name__ == '__main__':
# show some profiling information (requires the python-profiler package) # show some profiling information (requires the python-profiler package)
import cProfile import cProfile
import pstats import pstats
IS_VISIBLE = False
for index in range(3): for index in range(3):
print "Run: %d" % index print "Run: %d" % index
cProfile.run('get_devices_and_show()', 'profinfo') cProfile.run('get_devices_and_show()', 'profinfo')
p = pstats.Stats('profinfo') p = pstats.Stats('profinfo')
p.sort_stats('cumulative').print_stats(20) p.sort_stats('cumulative').print_stats(20)
else: else:
get_devices_and_show() print get_devices_and_show()

View File

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