* 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
parent
22021866b4
commit
7b0de22e3a
|
@ -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()
|
||||
|
||||
|
|
|
@ -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…
Reference in New Issue