diff --git a/README b/README index 3fc6cf7..4e0dbb0 100644 --- a/README +++ b/README @@ -1,3 +1,5 @@ AIF-NG (Arch Installation Framework, Next Generation) is a means to install Arch Linux (https://www.archlinux.org/) in an unattended and automated fashion. Think of it as something akin to RedHat's Kickstart or Debian's Preseed for Arch Linux. -See https://aif-ng.io/ for more information about this project. \ No newline at end of file +Be sure to import "aif" rather than importing any submodules directly, as deterministic logic is used to set up virtual names. + +See https://aif-ng.io/ for more information about this project. diff --git a/aif/constants_fallback.py b/aif/constants_fallback.py index ab41d27..1f14a93 100644 --- a/aif/constants_fallback.py +++ b/aif/constants_fallback.py @@ -1,3 +1,4 @@ +import re import uuid ## import parted # https://www.gnu.org/software/parted/api/index.html @@ -267,3 +268,16 @@ MSDOS_FSTYPE_IDS = ((1, 'Empty', b'\x00'), (98, 'Linux raid autodetect', b'\xFD'), (99, 'LANstep', b'\xFE'), (100, 'BBT', b'\xFF')) +MDADM_SUPPORTED_LEVELS = (0, 1, 4, 5, 6, 10) +MDADM_SUPPORTED_METADATA = ('0', '0.90', '1', '1.0', '1.1', '1.2', 'default', 'ddf', 'imsm') +MDADM_SUPPORTED_LAYOUTS = {5: (re.compile(r'^((left|right)-a?symmetric|[lr][as]|' + r'parity-(fir|la)st|' + r'ddf-(N|zero)-restart|ddf-N-continue)$'), + 'left-symmetric'), + 6: (re.compile(r'^((left|right)-a?symmetric(-6)?|[lr][as]|' + r'parity-(fir|la)st|' + r'ddf-(N|zero)-restart|ddf-N-continue|' + r'parity-first-6)$'), + None), + 10: (re.compile(r'^[nof][0-9]+$'), + None)} diff --git a/aif/disk/luks.py b/aif/disk/luks.py index 4736de8..784b815 100644 --- a/aif/disk/luks.py +++ b/aif/disk/luks.py @@ -1,3 +1,13 @@ from . import _common +import aif.disk.block as block +import aif.disk.lvm as lvm +import aif.disk.mdadm as mdadm + BlockDev = _common.BlockDev + + +class LUKS(object): + def __init__(self, partobj): + self.devpath = None + pass diff --git a/aif/disk/lvm.py b/aif/disk/lvm.py index 4736de8..06b3114 100644 --- a/aif/disk/lvm.py +++ b/aif/disk/lvm.py @@ -1,3 +1,24 @@ from . import _common +import aif.disk.block as block +import aif.disk.luks as luks +import aif.disk.mdadm as mdadm -BlockDev = _common.BlockDev + +_BlockDev = _common.BlockDev + + +class PV(object): + def __init__(self, partobj): + self.devpath = None + pass + + +class VG(object): + def __init__(self, vg_xml, lv_objs): + self.devpath = None + pass + + +class LV(object): + def __init__(self, lv_xml, pv_objs): + pass diff --git a/aif/disk/lvm_fallback.py b/aif/disk/lvm_fallback.py index 10799bc..2ac9f49 100644 --- a/aif/disk/lvm_fallback.py +++ b/aif/disk/lvm_fallback.py @@ -1,13 +1,4 @@ -try: - import gi - gi.require_version('BlockDev', '2.0') - from gi.repository import BlockDev, GLib - has_mod = True -except ImportError: - # This is ineffecient; the native gobject-introspection module is preferred. - # In Arch, this can be installed via the "extra" repository packages "libblockdev" and "python-gobject". - import subprocess - has_mod = False +import subprocess ## import aif.disk.block_fallback as block import aif.disk.luks_fallback as luks diff --git a/aif/disk/mdadm.py b/aif/disk/mdadm.py index 4736de8..babf511 100644 --- a/aif/disk/mdadm.py +++ b/aif/disk/mdadm.py @@ -1,3 +1,81 @@ +import re +## +import aif.utils +import aif.constants from . import _common +import aif.disk.block as block +import aif.disk.luks as luks +import aif.disk.lvm as lvm -BlockDev = _common.BlockDev + +_BlockDev = _common.BlockDev + + +_mdblock_size_re = re.compile(r'^(?P[0-9]+)\s+' + r'\((?P[0-9.]+)\s+GiB\s+' + r'(?P[0-9.]+)\s+GB\)') +_mdblock_unused_re = re.compile(r'^before=(?P[0-9]+)\s+sectors,' + r'\s+after=(?P[0-9]+)\s+sectors$') +_mdblock_badblock_re = re.compile(r'^(?P[0-9]+)\s+entries' + r'[A-Za-z\s]+' + r'(?P[0-9]+)\s+sectors$') + + +class Member(object): + def __init__(self, member_xml, partobj): + self.xml = member_xml + self.device = partobj + if not isinstance(self.device, (block.Partition, + block.Disk, + Array, + lvm.LV, + luks.LUKS)): + raise ValueError(('partobj must be of type ' + 'aif.disk.block.Disk, ' + 'aif.disk.block.Partition, ' + 'aif.disk.luks.LUKS, ' + 'aif.disk.lvm.LV, or' + 'aif.disk.mdadm.Array')) + self.devpath = self.device.devpath + self.is_superblocked = None + self.superblock = None + self._parseDeviceBlock() + + def _parseDeviceBlock(self): + pass + + def prepare(self): + pass + + +class Array(object): + def __init__(self, array_xml, homehost, devpath = None): + self.xml = array_xml + self.id = array_xml.attrib['id'] + self.level = int(self.xml.attrib['level']) + if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS: + raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i) + for i in + aif.constants.MDADM_SUPPORTED_LEVELS]))) + self.metadata = self.xml.attrib.get('meta', '1.2') + if self.metadata not in aif.constants.MDADM_SUPPORTED_METADATA: + raise ValueError('Metadata version must be one of: {0}'.format(', '.join( + aif.constants.MDADM_SUPPORTED_METADATA))) + + def addMember(self, memberobj): + pass + + def create(self): + pass + + def start(self, scan = False): + pass + + def stop(self): + pass + + def updateStatus(self): + pass + + def writeConf(self): + pass diff --git a/aif/disk/mdadm_fallback.py b/aif/disk/mdadm_fallback.py index fd6e417..8bdedf5 100644 --- a/aif/disk/mdadm_fallback.py +++ b/aif/disk/mdadm_fallback.py @@ -10,22 +10,10 @@ import mdstat import aif.disk.block_fallback as block import aif.disk.luks_fallback as luks import aif.disk.lvm_fallback as lvm +import aif.utils +import aif.constants -SUPPORTED_LEVELS = (0, 1, 4, 5, 6, 10) -SUPPORTED_METADATA = ('0', '0.90', '1', '1.0', '1.1', '1.2', 'default', 'ddf', 'imsm') -SUPPORTED_LAYOUTS = {5: (re.compile(r'^((left|right)-a?symmetric|[lr][as]|' - r'parity-(fir|la)st|' - r'ddf-(N|zero)-restart|ddf-N-continue)$'), - 'left-symmetric'), - 6: (re.compile(r'^((left|right)-a?symmetric(-6)?|[lr][as]|' - r'parity-(fir|la)st|' - r'ddf-(N|zero)-restart|ddf-N-continue|' - r'parity-first-6)$'), - None), - 10: (re.compile(r'^[nof][0-9]+$'), - None)} - _mdblock_size_re = re.compile(r'^(?P[0-9]+)\s+' r'\((?P[0-9.]+)\s+GiB\s+' r'(?P[0-9.]+)\s+GB\)') @@ -35,16 +23,6 @@ _mdblock_badblock_re = re.compile(r'^(?P[0-9]+)\s+entries' r'[A-Za-z\s]+' r'(?P[0-9]+)\s+sectors$') -def _itTakesTwo(n): - # So dumb. - isPowerOf2 = math.ceil(math.log(n, 2)) == math.floor(math.log(n, 2)) - return(isPowerOf2) - -def _safeChunks(n): - if (n % 4) != 0: - return(False) - return(True) - class Member(object): def __init__(self, member_xml, partobj): @@ -151,23 +129,26 @@ class Array(object): self.xml = array_xml self.id = array_xml.attrib['id'] self.level = int(self.xml.attrib['level']) - if self.level not in SUPPORTED_LEVELS: - raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i) for i in SUPPORTED_LEVELS]))) + if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS: + raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i) + for i in + aif.constants.MDADM_SUPPORTED_LEVELS]))) self.metadata = self.xml.attrib.get('meta', '1.2') - if self.metadata not in SUPPORTED_METADATA: - raise ValueError('Metadata version must be one of: {0}'.format(', '.join(SUPPORTED_METADATA))) + if self.metadata not in aif.constants.MDADM_SUPPORTED_METADATA: + raise ValueError('Metadata version must be one of: {0}'.format(', '.join( + aif.constants.MDADM_SUPPORTED_METADATA))) self.chunksize = int(self.xml.attrib.get('chunkSize', 512)) if self.level in (4, 5, 6, 10): - if not _itTakesTwo(self.chunksize): + if not aif.utils.isPowerofTwo(self.chunksize): # TODO: log.warn instead of raise exception? Will mdadm lose its marbles if it *isn't* a proper number? raise ValueError('chunksize must be a power of 2 for the RAID level you specified') if self.level in (0, 4, 5, 6, 10): - if not _safeChunks(self.chunksize): + if not aif.utils.hasSafeChunks(self.chunksize): # TODO: log.warn instead of raise exception? Will mdadm lose its marbles if it *isn't* a proper number? raise ValueError('chunksize must be divisible by 4 for the RAID level you specified') self.layout = self.xml.attrib.get('layout', 'none') - if self.level in SUPPORTED_LAYOUTS.keys(): - matcher, layout_default = SUPPORTED_LAYOUTS[self.level] + if self.level in aif.constants.MDADM_SUPPORTED_LAYOUTS.keys(): + matcher, layout_default = aif.constants.MDADM_SUPPORTED_LAYOUTS[self.level] if not matcher.search(self.layout): if layout_default: self.layout = layout_default @@ -190,20 +171,6 @@ class Array(object): self.members.append(memberobj) return() - def start(self, scan = False): - if not any((self.members, self.devpath)): - raise RuntimeError('Cannot assemble an array with no members (for hints) or device path') - cmd = ['mdadm', '--assemble', self.devpath] - if not scan: - for m in self.members: - cmd.append(m.devpath) - else: - cmd.append('--scan') - # TODO: logging! - subprocess.run(cmd) - self.state = 'assembled' - return() - def create(self): if not self.members: raise RuntimeError('Cannot create an array with no members') @@ -224,6 +191,20 @@ class Array(object): self.state = 'new' return() + def start(self, scan = False): + if not any((self.members, self.devpath)): + raise RuntimeError('Cannot assemble an array with no members (for hints) or device path') + cmd = ['mdadm', '--assemble', self.devpath] + if not scan: + for m in self.members: + cmd.append(m.devpath) + else: + cmd.append('--scan') + # TODO: logging! + subprocess.run(cmd) + self.state = 'assembled' + return() + def stop(self): # TODO: logging subprocess.run(['mdadm', '--stop', self.devpath]) diff --git a/aif/utils.py b/aif/utils.py index 7ffadbe..48ef2da 100644 --- a/aif/utils.py +++ b/aif/utils.py @@ -1,3 +1,4 @@ +import math import os import re import subprocess @@ -42,6 +43,18 @@ def hasBin(binary_name): return(False) +def hasSafeChunks(n): + if (n % 4) != 0: + return(False) + return(True) + + +def isPowerofTwo(n): + # So dumb. + isPowerOf2 = math.ceil(math.log(n, 2)) == math.floor(math.log(n, 2)) + return(isPowerOf2) + + def kernelFilesystems(): # I wish there was a better way of doing this. # https://unix.stackexchange.com/a/98680