import subprocess import os import logging import CryptoBoxTools import CryptoBoxPlugin class partition(CryptoBoxPlugin.CryptoBoxPlugin): 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): ## retrieve some values from 'args' - defaults are empty self.withConfigPartition = self.__isWithConfigPartition(args) self.device = self.__getSelectedDevice(args) self.deviceSize = self.__getAvailableDeviceSize(self.device) try: step = args["step"] del args["step"] except KeyError: step = "select_device" ## no (or invalid) device was supplied if not self.device: step == "select_device" if step == "add_partition": return self.__actionAddPartition(args) if step == "del_partition": return self.__actionDelPartition(args) elif step == "finish": return self.__actionFinish(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 __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""" # TODO: the config partition is ignored, as it is not part of the container list - that is not good 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): 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" % counter] = 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 not self.__runFDisk(parts): self.hdf["Data.Warning"] = "Plugins.partition.PartitioningFailed" return self.__actionAddPartition(args) else: # TODO: will we use the "yield"-style? If yes, then remove these messages (success and warning) #if self.__formatPartitions(parts): # self.hdf["Data.Success"] = "Plugins.partition.Partitioned" #else: # self.hdf["Data.Warning"] = "Plugins.partition.FormattingFailed" """ 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 self.cbox.reReadContainerList() ## return the result yield result return { "template": "show_format_progress", "generator": result_generator} else: return self.__actionAddPartition(args) 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 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""" if not device: return 0 rdev = os.stat(device).st_rdev minor = os.minor(rdev) major = os.major(rdev) for f in file("/proc/partitions"): try: elements = f.split() if len(elements) != 4: continue if (int(elements[0]) == major) and (int(elements[1]) == minor): deviceSize = int(elements[2])/1024 if self.withConfigPartition: deviceSize -= self.ConfigPartition["size"] return deviceSize except ValueError: pass return 0 def __isWithConfigPartition(self, args): try: if args["create_config_partition"]: createConfig = True else: createConfig = False except KeyError: createConfig = False return createConfig 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"] 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(os.path.dirname(__file__), "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): 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 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 if self.__formatOnePartition(dev_name, partType): yield "OK" else: yield "Failed!" 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 if self.__formatOnePartition(dev_name, partType): yield "OK" else: yield "Failed!" part_num += 1 del parts[0] return def __formatOnePartition(self, dev_name, type): proc = subprocess.Popen( shell = False, args = [ self.cbox.prefs["Programs"]["super"], self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "plugin", os.path.join(os.path.dirname(__file__), "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: 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(os.path.dirname(__file__), "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