diff --git a/src/cryptobox/core/blockdevice.py b/src/cryptobox/core/blockdevice.py index 590b799..a71e00c 100644 --- a/src/cryptobox/core/blockdevice.py +++ b/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() diff --git a/src/cryptobox/core/container.py b/src/cryptobox/core/container.py index e0c3f78..d461f54 100644 --- a/src/cryptobox/core/container.py +++ b/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")