diff --git a/.gitignore b/.gitignore index 72d4830..ba715fa 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ .*.swp .editix __pycache__/ +test.py diff --git a/aif/constants.py b/aif/constants.py index 8dc0424..aa7edf0 100644 --- a/aif/constants.py +++ b/aif/constants.py @@ -1,4 +1,8 @@ +import os +import re +## import parted PARTED_FSTYPES = list(dict(vars(parted.filesystem))['fileSystemType'].keys()) + diff --git a/aif/disk/__init__.py b/aif/disk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aif/disk.py b/aif/disk/block.py similarity index 77% rename from aif/disk.py rename to aif/disk/block.py index 9ead501..89a5cb0 100644 --- a/aif/disk.py +++ b/aif/disk/block.py @@ -18,10 +18,11 @@ import blkinfo import parted # https://www.gnu.org/software/parted/api/index.html import psutil ## -from .aif_util import xmlBool -from .constants import PARTED_FSTYPES +from aif.aif_util import xmlBool +PARTED_FSTYPES = list(dict(vars(parted.filesystem))['fileSystemType'].keys()) + # parted lib can do SI or IEC (see table to right at https://en.wikipedia.org/wiki/Binary_prefix) # We bit-shift to do conversions: # https://stackoverflow.com/a/12912296/733214 @@ -61,11 +62,24 @@ def convertSizeUnit(pos): class Partition(object): - def __init__(self, part_xml, diskobj, start_sector, partnum, tbltype): + def __init__(self, part_xml, diskobj, start_sector, partnum, tbltype, part_type = None): if tbltype not in ('gpt', 'msdos'): raise ValueError('{0} must be one of gpt or msdos'.format(tbltype)) + if tbltype == 'msdos' and part_type not in ('primary', 'extended', 'logical'): + raise ValueError(('You must specify if this is a ' + 'primary, extended, or logical partition for msdos partition tables')) self.xml = part_xml self.partnum = partnum + if tbltype == 'msdos': + if partnum > 4: + self.part_type = parted.PARTITION_LOGICAL + else: + if part_type == 'extended': + self.part_type = parted.PARTITION_EXTENDED + elif part_type == 'logical': + self.part_type = parted.PARTITION_LOGICAL + else: + self.part_type = parted.PARTITION_NORMAL self.fstype = self.xml.attrib['fsType'].lower() if self.fstype not in PARTED_FSTYPES: raise ValueError(('{0} is not a valid partition filesystem type; ' @@ -73,7 +87,8 @@ class Partition(object): ', '.join(sorted(PARTED_FSTYPES)))) self.disk = diskobj self.device = self.disk.device - self.dev = '{0}{1}'.format(self.device.path, partnum) + self.devpath = '{0}{1}'.format(self.device.path, partnum) + self.is_hiformatted = False sizes = {} for s in ('start', 'stop'): x = dict(zip(('from_bgn', 'size', 'type'), @@ -95,7 +110,7 @@ class Partition(object): if sizes['stop'][1]: self.end = sizes['stop'][0] + 0 else: - # This *technically* should be - 34, but the alignment optimizer fixes it for us. + # This *technically* should be - 34, at least for gpt, but the alignment optimizer fixes it for us. self.end = (self.device.getLength() - 1) - sizes['stop'][0] else: self.end = self.begin + sizes['stop'][0] @@ -107,7 +122,7 @@ class Partition(object): self.filesystem = parted.FileSystem(type = self.fstype, geometry = self.geometry) self.partition = parted.Partition(disk = diskobj, - type = parted.PARTITION_NORMAL, + type = self.part_type, geometry = self.geometry, fs = self.filesystem) if tbltype == 'gpt' and self.xml.attrib.get('name'): @@ -123,32 +138,22 @@ class Disk(object): def __init__(self, disk_xml): self.xml = disk_xml self.devpath = self.xml.attrib['device'] - self.partitions = [] self._initDisk() def _initDisk(self): + self.tabletype = self.xml.attrib.get('diskFormat', 'gpt').lower() + if self.tabletype in ('bios', 'mbr', 'dos'): + self.tabletype = 'msdos' + validlabels = parted.getLabels() + if self.tabletype not in validlabels: + raise ValueError(('Disk format {0} is not valid for this architecture;' + 'must be one of: {1}'.format(self.tabletype, ', '.join(list(validlabels))))) self.device = parted.getDevice(self.devpath) - try: - self.disk = parted.newDisk(self.device) - self.is_new = False - if xmlBool(self.xml.attrib.get('forceReformat')): - self.is_lowformatted = False - self.is_hiformatted = False - else: - self.is_lowformatted = True - self.is_hiformatted = False - for d in blkinfo.BlkDiskInfo().get_disks(filters = {'group': 'disk', - 'name': os.path.basename(self.devpath), - 'kname': os.path.basename(self.devpath)}): - if d.get('fstype', '').strip() != '': - self.is_hiformatted = True - break - except parted._ped.DiskException: - self.disk = None - self.is_new = True - self.is_lowformatted = False - self.is_hiformatted = False + self.disk = parted.freshDisk(self.device, self.tabletype) + self.is_lowformatted = False + self.is_hiformatted = False self.is_partitioned = False + self.partitions = [] return() def diskFormat(self): @@ -158,37 +163,34 @@ class Disk(object): for p in psutil.disk_partitions(all = True): if self.devpath in p: raise RuntimeError('{0} is mounted; we are cowardly refusing to low-format it'.format(self.devpath)) - if not self.is_new: - self.disk.deleteAllPartitions() - self.tabletype = self.xml.attrib.get('diskFormat', 'gpt').lower() - if self.tabletype in ('bios', 'mbr', 'dos'): - self.tabletype = 'msdos' - validlabels = parted.getLabels() - if self.tabletype not in validlabels: - raise ValueError(('Disk format {0} is not valid for this architecture;' - 'must be one of: {1}'.format(self.tabletype, ', '.join(list(validlabels))))) - self.disk = parted.freshDisk(self.device, self.tabletype) + self.disk.deleteAllPartitions() + self.disk.commit() self.is_lowformatted = True - return() - - def fsFormat(self): - if self.is_hiformatted: - return() - # This is a safeguard. We do *not* want to high-format a disk that is mounted. - for p in psutil.disk_partitions(all = True): - if self.devpath in p: - raise RuntimeError('{0} is mounted; we are cowardly refusing to high-format it'.format(self.devpath)) - # TODO! - pass + self.is_partitioned = False return() def getPartitions(self): # For GPT, this *technically* should be 34 -- or, more precisely, 2048 (see FAQ in manual), but the alignment # optimizer fixes it for us automatically. - start_sector = 0 + # But for DOS tables, it's required. + if self.tabletype == 'msdos': + start_sector = 2048 + else: + start_sector = 0 self.partitions = [] - for idx, part in enumerate(self.xml.findall('part')): - p = Partition(part, self.disk, start_sector, idx + 1, self.tabletype) + xml_partitions = self.xml.findall('part') + for idx, part in enumerate(xml_partitions): + partnum = idx + 1 + if self.tabletype == 'gpt': + p = Partition(part, self.disk, start_sector, partnum, self.tabletype) + else: + parttype = 'primary' + if len(xml_partitions) > 4: + if partnum == 4: + parttype = 'extended' + elif partnum > 4: + parttype = 'logical' + p = Partition(part, self.disk, start_sector, partnum, self.tabletype, part_type = parttype) start_sector = p.end + 1 self.partitions.append(p) return() @@ -208,6 +210,8 @@ class Disk(object): return() for p in self.partitions: self.disk.addPartition(partition = p, constraint = self.device.optimalAlignedConstraint) - self.disk.commit() + self.disk.commit() + p.devpath = p.partition.path + p.is_hiformatted = True self.is_partitioned = True return() diff --git a/aif/disk/filesystem.py b/aif/disk/filesystem.py new file mode 100644 index 0000000..ee45a2f --- /dev/null +++ b/aif/disk/filesystem.py @@ -0,0 +1,75 @@ +import os +import re +import subprocess +## +import psutil +## +from aif.disk.block import Partition +from aif.disk.luks import LUKS +from aif.disk.lvm import Group as LVMGroup +from aif.disk.mdadm import Array as MDArray + +# I wish there was a better way of doing this. +# https://unix.stackexchange.com/a/98680 +FS_FSTYPES = [] +with open('/proc/filesystems', 'r') as fh: + for line in fh.readlines(): + l = [i.strip() for i in line.split()] + if not l: + continue + if len(l) == 1: + FS_FSTYPES.append(l[0]) + else: + FS_FSTYPES.append(l[1]) +_mod_dir = os.path.join('/lib/modules', + os.uname().release, + 'kernel/fs') +_strip_mod_suffix = re.compile(r'(?P)\.ko(\.(x|g)?z))?$', re.IGNORECASE) +for i in os.listdir(_mod_dir): + path = os.path.join(_mod_dir, i) + fs_name = None + if os.path.isdir(path): + fs_name = i + elif os.path.isfile(path): + mod_name = _strip_mod_suffix.search(i) + fs_name = mod_name.group('fsname') + if fs_name: + # The kernel *probably* has autoloading enabled, but in case it doesn't... + # TODO: logging! + subprocess.run(['modprobe', fs_name]) + FS_FSTYPES.append(fs_name) + + +class FS(object): + def __init__(self, fs_xml, sourceobj): + self.xml = fs_xml + if not isinstance(sourceobj, (Partition, LUKS, LVMGroup, MDArray)): + raise ValueError(('sourceobj must be of type ' + 'aif.disk.block.Partition, ' + 'aif.disk.luks.LUKS, ' + 'aif.disk.lvm.Group, or' + 'aif.disk.mdadm.Array')) + self.source = sourceobj + self.devpath = sourceobj.devpath + self.formatted = False + self.fstype = self.xml.attrib.get('type') + + def format(self): + if self.formatted: + return () + # This is a safeguard. We do *not* want to high-format a disk that is mounted. + for p in psutil.disk_partitions(all = True): + if self.devpath in p: + raise RuntimeError(('{0} is mounted;' + 'we are cowardly refusing to apply a filesystem to it').format(self.devpath)) + # TODO! Logging + cmd = ['mkfs', + '-t', self.fstype] + for o in self.xml.findall('opt'): + cmd.append(o.attrib['name']) + if o.text: + cmd.append(o.text) + cmd.append(self.devpath) + subprocess.run(cmd) + self.is_hiformatted = True + return() diff --git a/aif/disk/luks.py b/aif/disk/luks.py new file mode 100644 index 0000000..e3d6d06 --- /dev/null +++ b/aif/disk/luks.py @@ -0,0 +1,4 @@ +class LUKS(object): + def __init__(self): + self.devpath = None + pass diff --git a/aif/disk/lvm.py b/aif/disk/lvm.py new file mode 100644 index 0000000..6f03d1a --- /dev/null +++ b/aif/disk/lvm.py @@ -0,0 +1,13 @@ +class PV(object): + def __init__(self, partobj): + self.devpath = None + pass + +class LV(object): + def __init__(self, lv_xml, pv_objs): + pass + +class Group(object): + def __init__(self, vg_xml, lv_objs): + self.devpath = None + pass diff --git a/aif/disk/mdadm.py b/aif/disk/mdadm.py new file mode 100644 index 0000000..29dc2a0 --- /dev/null +++ b/aif/disk/mdadm.py @@ -0,0 +1,11 @@ +class Member(object): + def __init__(self, member_xml, partobj): + self.xml = member_xml + self.device = partobj + self.devpath = self.device.devpath + pass + +class Array(object): + def __init__(self, array_xml): + self.devpath = None + pass diff --git a/docs/examples/aif.xml b/docs/examples/aif.xml index ff38747..ab66868 100644 --- a/docs/examples/aif.xml +++ b/docs/examples/aif.xml @@ -3,7 +3,7 @@ xmlns="http://aif-ng.io/" xsi:schemaLocation="http://aif-ng.io/aif.xsd"> - + @@ -12,11 +12,6 @@ - - - -F 32 -n {label} - - @@ -34,8 +29,27 @@ + + + + 32 + ESP + + + seekrit + + - + + + + lzo + + + 5 + / +