418 lines
14 KiB
Python
418 lines
14 KiB
Python
import subprocess
|
|
import os
|
|
import logging
|
|
import CryptoBoxTools
|
|
import CryptoBoxPlugin
|
|
|
|
class partition(CryptoBoxPlugin.CryptoBoxPlugin):
|
|
|
|
pluginCapabilities = [ "system" ]
|
|
pluginVisibility = [ "preferences" ]
|
|
requestAuth = True
|
|
rank = 80
|
|
|
|
PartTypes = {
|
|
"windows" : ["0xC", "vfat"],
|
|
"linux" : ["L", "ext3"]}
|
|
|
|
ConfigPartition = {
|
|
"size" : 5, # size of configuration partition (if necessary) in MB
|
|
"type" : "L",
|
|
"fs" : "ext2"}
|
|
|
|
|
|
def doAction(self, **args):
|
|
## load default hdf values
|
|
self.__prepareDataset()
|
|
## retrieve some values from 'args' - defaults are empty
|
|
self.device = self.__getSelectedDevice(args)
|
|
self.withConfigPartition = self.__isWithConfigPartition()
|
|
self.cbox.log.debug("partition plugin: selected device=%s" % str(self.device))
|
|
self.deviceSize = self.__getAvailableDeviceSize(self.device)
|
|
try:
|
|
step = args["step"]
|
|
del args["step"]
|
|
except KeyError:
|
|
step = "select_device"
|
|
try:
|
|
## this way of selecting the easy setup is necessary: see select_device.py for details
|
|
if args["easy"]: step = "easy"
|
|
except KeyError:
|
|
pass
|
|
## no (or invalid) device was supplied
|
|
if not self.device:
|
|
step = "select_device"
|
|
if step == "add_partition":
|
|
return self.__actionAddPartition(args)
|
|
elif step == "del_partition":
|
|
return self.__actionDelPartition(args)
|
|
elif step == "finish":
|
|
return self.__actionFinish(args)
|
|
elif step == "easy":
|
|
return self.__actionEasySetup(args)
|
|
else: # for "select_device" and for invalid targets
|
|
return self.__actionSelectDevice(args)
|
|
|
|
|
|
def getStatus(self):
|
|
return "%s / %s / %s" % (self.device, self.deviceSize, self.withConfigPartition)
|
|
|
|
|
|
def __prepareDataset(self):
|
|
self.hdf[self.hdf_prefix + "StyleSheetFile"] = os.path.join(self.pluginDir, "partition.css")
|
|
|
|
|
|
def __getSelectedDevice(self, args):
|
|
try:
|
|
device = args["block_device"]
|
|
except KeyError:
|
|
return None
|
|
if not self.__isDeviceValid(device):
|
|
return None
|
|
if self.__isDeviceBusy(device):
|
|
self.hdf["Data.Warning"] = "Plugins.partition.DiskIsBusy"
|
|
return None
|
|
return device
|
|
|
|
|
|
def __isDeviceValid(self, device):
|
|
if not device:
|
|
return False
|
|
if not self.cbox.isDeviceAllowed(device):
|
|
return False
|
|
if not device in CryptoBoxTools.getParentBlockDevices():
|
|
return False
|
|
return True
|
|
|
|
|
|
def __isDeviceBusy(self, device):
|
|
"""check if the device (or one of its partitions) is mounted"""
|
|
# the config partition is ignored, as it will get unmounted if necessary
|
|
import re
|
|
for c in self.cbox.getContainerList():
|
|
if re.match(device + "\d*$", c.getDevice()):
|
|
if c.isMounted(): return True
|
|
return False
|
|
|
|
|
|
def __actionSelectDevice(self, args):
|
|
import CryptoBoxTools
|
|
block_devices = [e
|
|
for e in CryptoBoxTools.getParentBlockDevices()
|
|
if self.cbox.isDeviceAllowed(e)]
|
|
counter = 0
|
|
for a in block_devices:
|
|
self.hdf[self.hdf_prefix + "BlockDevices.%d.name" % counter] = a
|
|
self.hdf[self.hdf_prefix + "BlockDevices.%d.size" % counter] = CryptoBoxTools.getBlockDeviceSizeHumanly(a)
|
|
self.cbox.log.debug("found a suitable block device: %s" % a)
|
|
counter += 1
|
|
if self.withConfigPartition:
|
|
self.hdf[self.hdf_prefix + "CreateConfigPartition"] = "1"
|
|
## there is no disk available
|
|
if not block_devices:
|
|
self.hdf["Data.Warning"] = "Plugins.partition.NoDisksAvailable"
|
|
return "select_device"
|
|
|
|
|
|
def __actionAddPartition(self, args):
|
|
self.hdf[self.hdf_prefix + "Device"] = self.device
|
|
self.hdf[self.hdf_prefix + "Device.Size"] = self.deviceSize
|
|
parts = self.__getPartitionsFromArgs(args)
|
|
self.__setPartitionData(parts)
|
|
return "set_partitions"
|
|
|
|
|
|
def __actionDelPartition(self, args):
|
|
try:
|
|
part_num = int(args["del_num"])
|
|
except (TypeError,KeyError):
|
|
return self.__actionAddPartition(args)
|
|
self.hdf[self.hdf_prefix + "Device"] = self.device
|
|
self.hdf[self.hdf_prefix + "Device.Size"] = self.deviceSize
|
|
parts = self.__getPartitionsFromArgs(args)
|
|
## valid partition number to be deleted?
|
|
if part_num < len(parts):
|
|
del parts[part_num]
|
|
else:
|
|
return self.__actionAddPartition(args)
|
|
self.__setPartitionData(parts)
|
|
return "set_partitions"
|
|
|
|
|
|
def __actionFinish(self, args):
|
|
parts = self.__getPartitionsFromArgs(args)
|
|
if parts:
|
|
self.__setPartitionData(parts)
|
|
if CryptoBoxTools.isPartOfBlockDevice(self.device, self.cbox.prefs.getActivePartition()):
|
|
self.cbox.prefs.umountPartition()
|
|
if not self.__runFDisk(parts):
|
|
self.hdf["Data.Warning"] = "Plugins.partition.PartitioningFailed"
|
|
self.cbox.log.warn("partition: failed to partition device: %s" % self.device)
|
|
return self.__actionAddPartition(args)
|
|
else:
|
|
"""
|
|
tricky problem: if the device was partitioned, then a created config partition is still part of the containerlist, as the label is not checked again - very ugly!!! So we will call reReadContainerList after formatting the last partition - see below
|
|
"""
|
|
self.cbox.reReadContainerList()
|
|
def result_generator():
|
|
counter = 0
|
|
## initialize the generator
|
|
formatPart_gen = self.__formatPartitions(parts)
|
|
while counter < len(parts):
|
|
## first part: get the device name
|
|
yield formatPart_gen.next()
|
|
counter += 1
|
|
## second part: do the real formatting of a partition
|
|
result = formatPart_gen.next()
|
|
## after the first partiton, we can reRead the containerList (as the possible config partition was already created)
|
|
if self.withConfigPartition and (counter == 1):
|
|
## important: reRead the containerList - but somehow it breaks the flow (hanging process)
|
|
#self.cbox.reReadContainerList()
|
|
## write config data
|
|
self.cbox.prefs.mountPartition()
|
|
self.cbox.prefs.write()
|
|
self.cbox.log.info("settings stored on config partition")
|
|
## return the result
|
|
if result:
|
|
yield "OK"
|
|
else:
|
|
yield "<b>Error</b>"
|
|
return {
|
|
"template": "show_format_progress",
|
|
"generator": result_generator}
|
|
else:
|
|
return self.__actionAddPartition(args)
|
|
|
|
|
|
def __actionEasySetup(self, args):
|
|
import types
|
|
## we do not have to take special care for a possible config partition
|
|
parts = [ { "size": self.deviceSize, "type": "windows" } ]
|
|
## umount partition if necessary
|
|
if CryptoBoxTools.isPartOfBlockDevice(self.device, self.cbox.prefs.getActivePartition()):
|
|
self.cbox.prefs.umountPartition()
|
|
## partition it
|
|
if not self.__runFDisk(parts):
|
|
self.hdf["Data.Warning"] = "Plugins.partition.PartitioningFailed"
|
|
return None
|
|
## "formatPartitions" is a generator, returning device names and bolean values
|
|
result = [e for e in self.__formatPartitions(parts) if type(e) == types.BooleanType]
|
|
if self.withConfigPartition:
|
|
self.cbox.prefs.mountPartition()
|
|
self.cbox.prefs.write()
|
|
## check if there is a "False" return value
|
|
if False in result:
|
|
## operation failed
|
|
self.hdf["Data.Warning"] = "Plugins.partition.FormattingFailed"
|
|
self.cbox.log.info("easy partitioning failed")
|
|
return "select_partitions"
|
|
else:
|
|
## operation was successful
|
|
self.hdf["Data.Success"] = "Plugins.partition.EasySetup"
|
|
self.cbox.log.info("easy partitioning succeeded")
|
|
## do not show the disk overview immediately - it does not get updated that fast
|
|
return { "plugin":"system_preferences", "values":[] }
|
|
|
|
|
|
def __setPartitionData(self, parts):
|
|
availSize = self.deviceSize
|
|
i = 0
|
|
for part in parts:
|
|
self.cbox.log.debug(part)
|
|
self.hdf[self.hdf_prefix + "Parts.%d.Size" % i] = part["size"]
|
|
self.hdf[self.hdf_prefix + "Parts.%d.Type" % i] = part["type"]
|
|
availSize -= part["size"]
|
|
i += 1
|
|
self.hdf[self.hdf_prefix + "availSize"] = availSize
|
|
if self.withConfigPartition:
|
|
self.hdf[self.hdf_prefix + "CreateConfigPartition"] = "1"
|
|
for t in self.PartTypes.keys():
|
|
self.hdf[self.hdf_prefix + "Types.%s" % t] = t
|
|
## store the currently existing partitions of the choosen block device
|
|
current_containers = [ e for e in self.cbox.getContainerList() if CryptoBoxTools.isPartOfBlockDevice(self.device, e.getDevice()) ]
|
|
for (index, t) in enumerate(current_containers):
|
|
self.hdf[self.hdf_prefix + "ExistingContainers.%d.name" % index] = t.getName()
|
|
self.hdf[self.hdf_prefix + "ExistingContainers.%d.size" % index] = CryptoBoxTools.getBlockDeviceSizeHumanly(t.getDevice())
|
|
|
|
|
|
def __getPartitionsFromArgs(self, args):
|
|
parts = []
|
|
done = False
|
|
availSize = self.deviceSize
|
|
i = -1
|
|
while not done:
|
|
i += 1
|
|
try:
|
|
size = int(args["part%d_size" % i])
|
|
partType = args["part%d_type" % i]
|
|
if int(size) > availSize:
|
|
self.hdf["Data.Warning"] = "Plugins.partition.PartitionTooBig"
|
|
continue
|
|
if int(size) < 10:
|
|
self.hdf["Data.Warning"] = "Plugins.partition.PartitionTooSmall"
|
|
continue
|
|
if not partType in self.PartTypes.keys(): continue
|
|
parts.append({"size":size, "type":partType})
|
|
availSize -= size
|
|
except TypeError:
|
|
pass
|
|
except KeyError:
|
|
done = True
|
|
return parts
|
|
|
|
|
|
def __getAvailableDeviceSize(self, device):
|
|
"""calculate the available size (MB) of the device
|
|
also consider a (possible) configuration partition"""
|
|
import CryptoBoxTools
|
|
deviceSize = CryptoBoxTools.getBlockDeviceSize(device)
|
|
if deviceSize < 0: return 0
|
|
if self.withConfigPartition:
|
|
deviceSize -= self.ConfigPartition["size"]
|
|
return deviceSize
|
|
|
|
|
|
def __isWithConfigPartition(self):
|
|
"""check if we have to create a configuration partition"""
|
|
if self.cbox.prefs.requiresPartition():
|
|
active = self.cbox.prefs.getActivePartition()
|
|
## we need a partition, if there is no active one
|
|
if not active: return True
|
|
## check if the active one is part of the current device
|
|
return CryptoBoxTools.isPartOfBlockDevice(self.device, active)
|
|
return False
|
|
|
|
|
|
def __runFDisk(self, parts):
|
|
## check if the device is completely filled (to avoid some empty last blocks)
|
|
avail_size = self.deviceSize
|
|
for d in parts: avail_size -= d["size"]
|
|
self.cbox.log.debug("remaining size: %d" % avail_size)
|
|
isFilled = avail_size == 0
|
|
proc = subprocess.Popen(
|
|
shell = False,
|
|
stdin = subprocess.PIPE,
|
|
stdout = subprocess.PIPE,
|
|
stderr = subprocess.PIPE,
|
|
args = [
|
|
self.cbox.prefs["Programs"]["super"],
|
|
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
|
"plugin",
|
|
os.path.join(self.pluginDir, "root_action.py"),
|
|
"partition",
|
|
self.device])
|
|
for line in self.__getSFDiskLayout(parts, isFilled):
|
|
proc.stdin.write(line + "\n")
|
|
(output, error) = proc.communicate()
|
|
if proc.returncode != 0: self.cbox.log.debug("partitioning failed: %s" % error)
|
|
return proc.returncode == 0
|
|
|
|
|
|
def __getSFDiskLayout(self, paramParts, isFilled):
|
|
"""this generator returns the input lines for sfdisk"""
|
|
parts = paramParts[:]
|
|
## first a (possible) configuration partition - so it will be reusable
|
|
if self.withConfigPartition:
|
|
## fill the main table (including a config partition)
|
|
yield ",%d,%s" % (self.ConfigPartition["size"], self.ConfigPartition["type"])
|
|
## one primary partition
|
|
if isFilled and (len(parts) == 1):
|
|
## fill the rest of the device
|
|
yield ",,%s,*" % self.PartTypes[parts[0]["type"]][0]
|
|
else:
|
|
## only use the specified size
|
|
yield ",%d,%s,*" % (parts[0]["size"], self.PartTypes[parts[0]["type"]][0])
|
|
del parts[0]
|
|
## no extended partition, if there is only one disk
|
|
if not parts: return
|
|
## an extended container for the rest
|
|
yield ",,E"
|
|
## an empty partition in main table
|
|
yield ";"
|
|
## maybe another empty partition if there is no config partition
|
|
if not self.withConfigPartition: yield ";"
|
|
while parts:
|
|
if isFilled and (len(parts) == 1):
|
|
yield ",,%s" % (self.PartTypes[parts[0]["type"]][0],)
|
|
else:
|
|
yield ",%d,%s" % (parts[0]["size"], self.PartTypes[parts[0]["type"]][0])
|
|
del parts[0]
|
|
|
|
|
|
def __formatPartitions(self, paramParts):
|
|
import threading
|
|
parts = paramParts[:]
|
|
part_num = 1
|
|
## maybe a config partition?
|
|
if self.withConfigPartition:
|
|
dev_name = self.device + str(part_num)
|
|
self.cbox.log.info("formatting config partition (%s)" % dev_name)
|
|
if self.__formatOnePartition(dev_name, self.ConfigPartition["fs"]):
|
|
self.__setLabelOfPartition(dev_name, self.cbox.prefs["Main"]["ConfigVolumeLabel"])
|
|
part_num += 1
|
|
## the first data partition
|
|
dev_name = self.device + str(part_num)
|
|
partType = self.PartTypes[parts[0]["type"]][1]
|
|
self.cbox.log.info("formatting partition (%s) as '%s'" % (dev_name, partType))
|
|
yield dev_name
|
|
yield self.__formatOnePartition(dev_name, partType)
|
|
del parts[0]
|
|
## other data partitions
|
|
part_num = 5
|
|
while parts:
|
|
dev_name = self.device + str(part_num)
|
|
partType = self.PartTypes[parts[0]["type"]][1]
|
|
self.cbox.log.info("formatting partition (%s) as '%s'" % (dev_name, partType))
|
|
yield dev_name
|
|
yield self.__formatOnePartition(dev_name, partType)
|
|
part_num += 1
|
|
del parts[0]
|
|
return
|
|
|
|
|
|
def __formatOnePartition(self, dev_name, type):
|
|
## first: retrieve UUID - it can be removed from the database afterwards
|
|
volDB = self.cbox.prefs.volumesDB
|
|
prev_name = [e.getName() for e in self.cbox.getContainerList() if e.getDevice() == dev_name]
|
|
## call "mkfs"
|
|
proc = subprocess.Popen(
|
|
shell = False,
|
|
args = [
|
|
self.cbox.prefs["Programs"]["super"],
|
|
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
|
"plugin",
|
|
os.path.join(self.pluginDir, "root_action.py"),
|
|
"format",
|
|
dev_name,
|
|
type])
|
|
(output, error) = proc.communicate()
|
|
if proc.returncode != 0:
|
|
self.cbox.log.warn("failed to create filesystem on %s: %s" % (dev_name, error))
|
|
return False
|
|
else:
|
|
## remove unused volume entry
|
|
if prev_name:
|
|
self.cbox.prefs.volumesDB[prev_name[0]]
|
|
return True
|
|
|
|
|
|
def __setLabelOfPartition(self, dev_name, label):
|
|
proc = subprocess.Popen(
|
|
shell = False,
|
|
stdout = subprocess.PIPE,
|
|
stderr = subprocess.PIPE,
|
|
args = [
|
|
self.cbox.prefs["Programs"]["super"],
|
|
self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
|
|
"plugin",
|
|
os.path.join(self.pluginDir, "root_action.py"),
|
|
"label",
|
|
dev_name,
|
|
label])
|
|
(output, error) = proc.communicate()
|
|
if proc.returncode == 0:
|
|
return True
|
|
else:
|
|
self.cbox.log.warn("failed to create filesystem on %s: %s" % (dev_name, error))
|
|
return False
|
|
|