whew. finally done block.py.
the msdos table primary/extended/logical thing was a pain but the logic wasn't too bad.
This commit is contained in:
parent
305a0db34f
commit
9dada73cf0
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@
|
||||
.*.swp
|
||||
.editix
|
||||
__pycache__/
|
||||
test.py
|
||||
|
@ -1,4 +1,8 @@
|
||||
import os
|
||||
import re
|
||||
##
|
||||
import parted
|
||||
|
||||
|
||||
PARTED_FSTYPES = list(dict(vars(parted.filesystem))['fileSystemType'].keys())
|
||||
|
||||
|
0
aif/disk/__init__.py
Normal file
0
aif/disk/__init__.py
Normal file
@ -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.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.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.
|
||||
# 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()
|
||||
@ -209,5 +211,7 @@ class Disk(object):
|
||||
for p in self.partitions:
|
||||
self.disk.addPartition(partition = p, constraint = self.device.optimalAlignedConstraint)
|
||||
self.disk.commit()
|
||||
p.devpath = p.partition.path
|
||||
p.is_hiformatted = True
|
||||
self.is_partitioned = True
|
||||
return()
|
75
aif/disk/filesystem.py
Normal file
75
aif/disk/filesystem.py
Normal file
@ -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<fsname>)\.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()
|
4
aif/disk/luks.py
Normal file
4
aif/disk/luks.py
Normal file
@ -0,0 +1,4 @@
|
||||
class LUKS(object):
|
||||
def __init__(self):
|
||||
self.devpath = None
|
||||
pass
|
13
aif/disk/lvm.py
Normal file
13
aif/disk/lvm.py
Normal file
@ -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
|
11
aif/disk/mdadm.py
Normal file
11
aif/disk/mdadm.py
Normal file
@ -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
|
@ -3,7 +3,7 @@
|
||||
xmlns="http://aif-ng.io/"
|
||||
xsi:schemaLocation="http://aif-ng.io/aif.xsd">
|
||||
<storage>
|
||||
<disk device="/dev/sda" diskFormat="gpt" forceReformat="true">
|
||||
<disk device="/dev/sda" diskFormat="gpt">
|
||||
<!-- Partitions are numbered *in the order they are specified*. -->
|
||||
<part id="boot" name="BOOT" label="/boot" start="0%" stop="10%" fsType="ef00"/><!-- e.g. this would be /dev/sda1 -->
|
||||
<part id="secrets1" name="crypted" label="shh" start="10%" stop="20%" fsType="8300"/>
|
||||
@ -12,11 +12,6 @@
|
||||
<part id="raid1_d2" start="55%" stop="80%" fsType="fd00"/>
|
||||
<part id="swap" start="80%" stop="100%" fsType="8200" />
|
||||
</disk>
|
||||
<fileSystems>
|
||||
<fs source="boot" type="vfat">
|
||||
<opts>-F 32 -n {label}</opts>
|
||||
</fs>
|
||||
</fileSystems>
|
||||
<!-- "Special" devices are processed *in the order they are specified*. This is important if you wish to
|
||||
e.g. layer LUKS on top of LVM - you would specify <lvm> before <luks> and reference the
|
||||
<luksDev id="SOMETHING" ... > as <lvmLogical source="SOMETHING" ... />. -->
|
||||
@ -34,8 +29,27 @@
|
||||
<member source="raid1_d2"/>
|
||||
</array>
|
||||
</mdadm>
|
||||
<fileSystems>
|
||||
<fs source="boot" type="vfat">
|
||||
<!-- Supports mkfs arguments. Leave off the filesystem type and device name, obviously;
|
||||
those are handled by the above attributes. -->
|
||||
<opt name="-F">32</opt>
|
||||
<opt name="-n">ESP</opt>
|
||||
</fs>
|
||||
<fs source="luks_secrets" type="ext4">
|
||||
<opt name="-L">seekrit</opt>
|
||||
</fs>
|
||||
</fileSystems>
|
||||
<!-- And you use the id to reference mountpoints as well. -->
|
||||
<mount source="luks_secrets" target="/mnt/aif" order="1" />
|
||||
<mount source="luks_secrets" target="/mnt/aif" order="1">
|
||||
<opt name="rw"/>
|
||||
<opt name="relatime"/>
|
||||
<opt name="compress">lzo</opt>
|
||||
<opt name="ssd"/>
|
||||
<opt name="space_cache"/>
|
||||
<opt name="subvolid">5</opt>
|
||||
<opt name="subvol">/</opt>
|
||||
</mount>
|
||||
<mount source="boot" target="/mnt/aif/boot" order="2" />
|
||||
<mount source="swap" target="swap" order="3" />
|
||||
<mount source="vg1" target="/mnt/aif/mnt/pool" order="4" />
|
||||
|
Loading…
Reference in New Issue
Block a user