diff --git a/src/cryptobox/core/blockdevice.py b/src/cryptobox/core/blockdevice.py index 3c8df41..e5ee214 100644 --- a/src/cryptobox/core/blockdevice.py +++ b/src/cryptobox/core/blockdevice.py @@ -22,6 +22,9 @@ These classes detect and filter available blockdevices. ''' + +#TODO: call blkid only once for all devices and scan the result + __revision__ = "$Id$" @@ -111,11 +114,14 @@ class Blockdevice: self.devnodes = None self.uuid = None self.label = None - self.reset() + self.reset(empty_cache=False) - def reset(self): + def reset(self, empty_cache=True): """reread the data of the device + + usually we will have to reset the cache, too + just in case of first-time initialization, this is not necessary """ CACHE.reset(["blockdevice_info", self.name]) self.devnum = self.__get_major_minor() @@ -128,6 +134,7 @@ class Blockdevice: self.devnodes = self.__get_device_nodes() self.uuid = self.__get_uuid() self.label = self.__get_label() + self.type_id = self.__get_type_id() def is_valid(self): @@ -569,8 +576,49 @@ class Blockdevice: self.devnodes[0]]) (output, error) = proc.communicate() except OSError, err_msg: - LOGGER.warning("Failed to call '%s' to determine label: %s" % \ - (prefs["Programs"]["blkid"], err_msg)) + LOGGER.warning("Failed to call '%s' to determine label for " \ + % prefs["Programs"]["blkid"] + "'%s': %s" % \ + (self.devnodes[0], err_msg)) + return None + if proc.returncode != 0: + LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \ + (prefs["Programs"]["blkid"], self.devnodes[0], + error.strip())) + return None + result = output.strip() + if result: + return result + else: + return None + + + def __get_type_id(self): + """determine the type id of a filesystem contained in a device + + possible results are: ext2, ext3, vfat, reiserfs, swap, ... + return None for errors, empty labels and for luks or non-storage devices + """ + if not self.is_valid(): + return None + if self.is_luks(): + return None + prefs = _load_preferences() + try: + proc = subprocess.Popen( + shell = False, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + args = [ prefs["Programs"]["blkid"], + "-s", "TYPE", + "-o", "value", + "-c", os.devnull, + "-w", os.devnull, + self.devnodes[0]]) + (output, error) = proc.communicate() + except OSError, err_msg: + LOGGER.warning("Failed to call '%s' to determine type id for" \ + % prefs["Programs"]["blkid"] + " '%s': %s" % \ + (self.devnodes[0], err_msg)) return None if proc.returncode != 0: LOGGER.warning("Execution of '%s' for '%s' failed: %s" % \ diff --git a/src/cryptobox/core/container.py b/src/cryptobox/core/container.py index c5f13c5..088205c 100644 --- a/src/cryptobox/core/container.py +++ b/src/cryptobox/core/container.py @@ -28,6 +28,7 @@ import os import re import time from cryptobox.core.exceptions import * +import cryptobox.core.blockdevice CONTAINERTYPES = { @@ -51,8 +52,6 @@ class CryptoBoxContainer: """Manage a container of the CryptoBox """ - __dmDir = "/dev/mapper" - def __init__(self, device, cbox): """initialize the container @@ -68,7 +67,7 @@ class CryptoBoxContainer: self.mount = None self.umount = None self.attributes = None - self.reset_object() + self.reset_object(reset_device=False) def get_name(self): @@ -205,13 +204,15 @@ class CryptoBoxContainer: return self.device.size - def reset_object(self): + def reset_object(self, reset_device=True): """recheck the information about this container this is especially useful after changing the type via 'create' + the 'device' attribute does not need to be reset during __init__ Available since: 0.3.0 """ - self.device.reset() + if reset_device(): + self.device.reset() self.uuid = self.__get_uuid() self.cont_type = self.__get_type_of_partition() self.fs_type = self.__get_fs_type() @@ -243,7 +244,8 @@ class CryptoBoxContainer: ## no exception was raised during creation -> we can continue ## reset the properties (encryption state, ...) of the device self.reset_object() - ## restore the old name (must be after reset_object) + ## restore the old name, as the uuid was changed during 'create' + ## must happen after reset_object try: self.set_name(old_name) except CBNameIsInUse: @@ -266,6 +268,7 @@ class CryptoBoxContainer: ## return if new and old passwords are the same if oldpw == newpw: return + #TODO: why can we do this only for non-mounted volumes? if self.is_mounted(): raise CBVolumeIsActive("this container is currently active") ## remove any potential open luks mapping @@ -339,13 +342,18 @@ class CryptoBoxContainer: def __get_name_of_container(self): """retrieve the name of the container by querying the database - call this function only for the initial setup of the container object""" + call this function only for the initial setup of the container object + """ found_name = None for key in self.cbox.prefs.volumes_db.keys(): if self.cbox.prefs.volumes_db[key]["uuid"] == self.uuid: found_name = key + ## the name may not be equal to the name of another existing device + ## otherwise problems regarding the mount directory would arise if found_name: - return found_name + test_device = cryptobox.core.blockdevice.get_blockdevice(found_name) + if (test_device is None) or (test_device == self.device): + return found_name ## there is no name defined for this uuid - we will propose a good one prefix = self.cbox.prefs["Main"]["DefaultVolumePrefix"] unused_found = False @@ -388,62 +396,32 @@ class CryptoBoxContainer: def __get_type_id_of_partition(self): "returns the type of the partition (see 'man blkid')" - proc = subprocess.Popen( - shell = False, - stdout = subprocess.PIPE, - stderr = subprocess.PIPE, - args = [ self.cbox.prefs["Programs"]["blkid"], - "-s", "TYPE", - "-o", "value", - "-c", os.devnull, - "-w", os.devnull, - self.get_device() ]) - (stdout, stderr) = proc.communicate() - if proc.returncode == 0: - ## we found a uuid - return stdout.strip() - elif proc.returncode == 2: - ## failed to find the attribute - no problem - return None - else: - ## something strange happened - self.cbox.log.warn("retrieving of partition type via 'blkid' failed: %s" % \ - (stderr.strip(), )) - return None + return self.device.type_id def __get_fs_type(self): - "returns the filesystem used on a container" - ## should we handle device mapping or plain device - 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" + """returns the filesystem used on a container + + for luks devices: return the type of the encrypted container + return None for invalid device, for non-storage devices, ... + """ + if self.device.is_luks(): + ## luks devices need special care ... + if self.device.holders: + return cryptobox.core.blockdevice.get_blockdevice( + self.device.holders[0]).type_id + else: + ## the encrypted container is not open + return None else: - container = self.get_device() - proc = subprocess.Popen( - shell = False, - stdout = subprocess.PIPE, - stderr = subprocess.PIPE, - args = [ self.cbox.prefs["Programs"]["blkid"], - "-s","TYPE", - container ]) - (stdout, stderr) = proc.communicate() - if proc.returncode == 0: - return stdout.split("TYPE=")[1] - else: - ## if something goes wrong don't dig deeper - self.cbox.log.warn("Filesystem determination (%s) failed: %s" % \ - (self.get_device(), stderr.strip())) - return "undetermined" - return + ## common (non-luks) devices + return self.device.type_id 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) + return os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], + self.name) def __mount_luks(self, password): @@ -457,10 +435,12 @@ class CryptoBoxContainer: if not os.path.exists(self.__get_mount_point()): self.__create_mount_directory(self.__get_mount_point()) if not os.path.exists(self.__get_mount_point()): - err_msg = "Could not create mountpoint (%s)" % (self.__get_mount_point(), ) + err_msg = "Could not create mountpoint (%s)" % \ + (self.__get_mount_point(), ) self.cbox.log.error(err_msg) raise CBMountError(err_msg) - self.cbox.send_event_notification("premount", self.__get_event_args()) + self.cbox.send_event_notification("premount", + self.__get_event_args()) proc = subprocess.Popen( shell = False, stdin = subprocess.PIPE, @@ -480,6 +460,17 @@ class CryptoBoxContainer: err_msg = "Could not open the luks mapping: %s" % (errout.strip(), ) self.cbox.log.warn(err_msg) raise CBMountError(err_msg) + ## reset device info (reread self.device.holders) + self.device.reset() + if self.device.holders: + ## the decrypted blockdevice is available + plain_device = cryptobox.core.blockdevice.get_blockdevice( + self.device.holders[0]).devnodes[0] + else: + err_msg = "Could not find the plaintext container for " \ + + "'%s': %s" % (self.get_device(), "no hold devices found") + self.cbox.log.warn(err_msg) + raise CBMountError(err_msg) proc = subprocess.Popen( shell = False, stdin = None, @@ -489,20 +480,23 @@ class CryptoBoxContainer: self.cbox.prefs["Programs"]["super"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "program", "mount", - os.path.join(self.__dmDir, self.name), + plain_device, self.__get_mount_point()]) proc.wait() if proc.returncode != 0: - err_msg = "Could not mount the filesystem: %s" % (proc.stderr.read().strip(), ) + err_msg = "Could not mount the filesystem: %s" \ + % (proc.stderr.read().strip(), ) self.cbox.log.warn(err_msg) raise CBMountError(err_msg) - ## chmod the mount directory to 0777 - this is the easy way to avoid problems - ## this only works for ext2/3 - vfat silently ignore it + ## chmod the mount directory to 0777 + ## this is the easy way to avoid problems + ## it only works for ext2/3 - vfat silently ignore it ## we mounted vfat partitions with umask=0000 try: os.chmod(self.__get_mount_point(), 0777) except OSError: - self.cbox.log.warn("Failed to set write permission for the mount directory") + self.cbox.log.warn("Failed to set write permission for the " \ + + "mount directory") self.cbox.send_event_notification("postmount", self.__get_event_args()) @@ -522,10 +516,14 @@ class CryptoBoxContainer: self.__get_mount_point()]) proc.wait() if proc.returncode != 0: - err_msg = "Could not umount the filesystem: %s" % (proc.stderr.read().strip(), ) + err_msg = "Could not umount the filesystem: %s" % \ + (proc.stderr.read().strip(), ) self.cbox.log.warn(err_msg) raise CBUmountError(err_msg) - if os.path.exists(os.path.join(self.__dmDir, self.name)): + ## reset device (reread self.device.holders) + self.device.reset() + ## are there any dependent devices? + if self.device.holders: proc = subprocess.Popen( shell = False, stdin = None, @@ -574,8 +572,9 @@ class CryptoBoxContainer: err_msg = "Could not mount the filesystem: %s" % (proc.stderr.read().strip(), ) self.cbox.log.warn(err_msg) raise CBMountError(err_msg) - ## chmod the mount directory to 0777 - this is the easy way to avoid problems - ## this only works for ext2/3 - vfat silently ignore it + ## chmod the mount directory to 0777 + ## this is the easy way to avoid problems + ## it only works for ext2/3 - vfat silently ignore it ## we mounted vfat partitions with umask=0000 try: os.chmod(self.__get_mount_point(), 0777) @@ -712,8 +711,19 @@ class CryptoBoxContainer: err_msg = "Could not open the new luks mapping: %s" % (errout.strip(), ) self.cbox.log.error(err_msg) raise CBCreateError(err_msg) + ## reset device info (reread self.device.holders) + self.device.reset() + if self.device.holders: + ## the decrypted blockdevice is available + plain_device = cryptobox.core.blockdevice.get_blockdevice( + self.device.holders[0]).devnodes[0] + else: + err_msg = "Could not find the plaintext container for " \ + + "'%s': %s" % (self.get_device(), "no hold devices found") + self.cbox.log.warn(err_msg) + raise CBMountError(err_msg) def format_luks(): - """This function will get called as a seperate thread. + """This function will get called as a separate thread. To avoid the non-sharing cpu distribution between the formatting thread and the main interface, we fork and let the parent wait for the child. @@ -735,7 +745,7 @@ class CryptoBoxContainer: self.cbox.prefs["Programs"]["nice"], self.cbox.prefs["Programs"]["mkfs"], "-t", fs_type, - os.path.join(self.__dmDir, self.name)]) + plain_device ] ) loc_data.proc.wait() ## wait to allow error detection if loc_data.proc.returncode == 0: @@ -767,15 +777,16 @@ class CryptoBoxContainer: if (not os.path.islink(abs_dir)) \ and os.path.isdir(abs_dir) \ and (not os.path.ismount(abs_dir)) \ - and (os.path.isfile(os.path.join(abs_dir,MOUNT_DIR_MARKER))) \ + and (os.path.isfile(os.path.join(abs_dir, + MOUNT_DIR_MARKER))) \ and (len(os.listdir(abs_dir)) == 1): try: os.remove(os.path.join(abs_dir, MOUNT_DIR_MARKER)) os.rmdir(abs_dir) except OSError, err_msg: ## we do not care too much about unclean cleaning ... - self.cbox.log.info("failed to clean a mountpoint (%s): %s" % \ - (abs_dir, str(err_msg))) + self.cbox.log.info("failed to clean a mountpoint (%s): %s" \ + % (abs_dir, str(err_msg))) def __create_mount_directory(self, dirname): @@ -787,7 +798,8 @@ class CryptoBoxContainer: mark_file.close() except OSError, err_msg: ## we do not care too much about the marking - self.cbox.log.info("failed to mark a mountpoint (%s): %s" % (dirname, str(err_msg))) + self.cbox.log.info("failed to mark a mountpoint (%s): %s" % \ + (dirname, str(err_msg))) def __get_event_args(self): @@ -797,5 +809,6 @@ class CryptoBoxContainer: """ type_text = [e for e in CONTAINERTYPES.keys() if CONTAINERTYPES[e] == self.get_type()][0] - return [self.get_device(), self.get_name(), type_text, self.__get_mount_point()] + return [self.get_device(), self.get_name(), type_text, + self.__get_mount_point()]