← Back to team overview

curtin-dev team mailing list archive

Re: [Merge] ~mwhudson/curtin:v2-partition-ops into curtin:master

 

A few minor requests.

Diff comments:

> diff --git a/curtin/commands/block_meta_v2.py b/curtin/commands/block_meta_v2.py
> index 25f777b..72d862d 100644
> --- a/curtin/commands/block_meta_v2.py
> +++ b/curtin/commands/block_meta_v2.py
> @@ -1,17 +1,246 @@
>  # This file is part of curtin. See LICENSE file for copyright and license info.
>  
> +from typing import Optional
> +
> +import attr
> +
> +from curtin import (block, util)
>  from curtin.commands.block_meta import (
>      disk_handler as disk_handler_v1,
> +    get_path_to_storage_volume,
>      partition_handler as partition_handler_v1,
> +    partition_verify_sfdisk,
> +    )
> +from curtin.log import LOG
> +from curtin.storage_config import (
> +    GPT_GUID_TO_CURTIN_MAP,
>      )
>  
>  
> +@attr.s(auto_attribs=True)
> +class PartTableEntry:
> +    number: int
> +    start: int
> +    size: int
> +    type: str
> +    uuid: Optional[str]
> +    bootable: bool = False
> +
> +    def render(self):
> +        r = f'{self.number}: '
> +        for a in 'start', 'size', 'type', 'uuid':
> +            v = getattr(self, a)
> +            if v is not None:
> +                r += f' {a}={v}'
> +        if self.bootable:
> +            r += ' bootable'

I wonder if we should instead do a strict key=value system to make it easier on other parsers - they won't have to keep current on a list of known flags, which are prone to changes as we support new stuff.  If that isn't a serious concern than I'm fine with this system.

> +        return r
> +
> +
> +ONE_MB_BYTES = 1 << 20

I'm going to be that guy and ask for MiB instead of MB

> +SECTOR_BYTES = 512
> +ONE_MB_SECTORS = ONE_MB_BYTES // SECTOR_BYTES
> +
> +
> +def align_up(size, block_size):

Can we get some quick tests on these align functions?

> +    return (size + block_size - 1) & ~(block_size - 1)
> +
> +
> +def align_down(size, block_size):
> +    return size & ~(block_size - 1)
> +
> +
> +FLAG_TO_GUID = {
> +    flag: guid for (guid, (flag, typecode)) in GPT_GUID_TO_CURTIN_MAP.items()
> +    }
> +
> +
> +class SFDiskPartTable:
> +
> +    def __init__(self):
> +        self.entries = []
> +
> +    def render(self):
> +        r = ['label: ' + self.label, ''] + [e.render() for e in self.entries]
> +        return '\n'.join(r)
> +
> +    def apply(self, device):
> +        sfdisk_script = self.render()
> +        LOG.debug("sfdisk input:\n---\n%s\n---\n", sfdisk_script)
> +        util.subp(
> +            ['sfdisk', '--lock', '--no-tell', device],
> +            data=sfdisk_script.encode('ascii'))
> +        util.subp(['partprobe', device])
> +
> +
> +class GPTPartTable(SFDiskPartTable):
> +
> +    label = 'gpt'
> +
> +    def add(self, action):
> +        number = action.get('number', len(self.entries) + 1)
> +        if 'offset' in action:
> +            start = int(util.human2bytes(action['offset'])) // SECTOR_BYTES
> +        else:
> +            if self.entries:
> +                prev = self.entries[-1]
> +                start = align_up(prev.start + prev.size, ONE_MB_SECTORS)
> +            else:
> +                start = ONE_MB_SECTORS
> +        size = int(util.human2bytes(action['size'])) // SECTOR_BYTES
> +        uuid = action.get('uuid')
> +        type = FLAG_TO_GUID.get(action.get('flag'))
> +        entry = PartTableEntry(number, start, size, type, uuid)
> +        self.entries.append(entry)
> +        return entry
> +
> +
> +class DOSPartTable(SFDiskPartTable):
> +
> +    label = 'dos'
> +    _extended = None
> +
> +    def add(self, action):
> +        flag = action.get('flag', None)
> +        start = action.get('offset', None)
> +        if start is not None:
> +            start = int(util.human2bytes(start)) // SECTOR_BYTES
> +        if flag == 'logical':
> +            if self._extended is None:
> +                raise Exception("logical partition without extended partition")
> +            prev = None
> +            for entry in reversed(self.entries):
> +                if entry.number > 4:
> +                    prev = entry
> +                    break
> +            # The number of an logical partition cannot be specified (so the
> +            # 'number' from the action is completely ignored here) as the
> +            # partitions are numbered by the order they are found in the linked
> +            # list of logical partitions. sfdisk just cares that we put a
> +            # number > 4 here, in fact we could "number" every logical
> +            # partition as "5" but it's not hard to put the number that the
> +            # partition will end up getting into the sfdisk input.
> +            if prev is None:
> +                number = 5
> +                if start is None:
> +                    start = align_up(
> +                        self._extended.start + ONE_MB_SECTORS, ONE_MB_SECTORS)
> +            else:
> +                number = prev.number + 1
> +                if start is None:
> +                    start = align_up(
> +                        prev.start + prev.size + ONE_MB_SECTORS,
> +                        ONE_MB_SECTORS)
> +        else:
> +            number = action.get('number', len(self.entries) + 1)
> +            if number > 4:
> +                raise Exception(
> +                    "primary partition cannot have number %s" % (number,))
> +            if start is None:
> +                prev = None
> +                for entry in self.entries:
> +                    if entry.number <= 4:
> +                        prev = entry
> +                        break
> +                if prev is None:
> +                    start = ONE_MB_SECTORS
> +                else:
> +                    start = align_up(prev.start + prev.size, ONE_MB_SECTORS)
> +        size = int(util.human2bytes(action['size'])) // SECTOR_BYTES
> +        FLAG_TO_TYPE = {
> +            'extended': 'extended',
> +            'boot': 'uefi',
> +            'swap': 'swap',
> +            'lvm': 'lvm',
> +            'raid': 'raid',
> +            }
> +        type = FLAG_TO_TYPE.get(flag)
> +        if flag == 'boot':
> +            bootable = True
> +        else:
> +            bootable = None
> +        entry = PartTableEntry(
> +            number, start, size, type, uuid=None, bootable=bootable)
> +        if flag == 'extended':
> +            self._extended = entry
> +        self.entries.append(entry)
> +        return entry
> +
> +
> +def _find_part_info(sfdisk_info, offset):
> +    for part in sfdisk_info['partitions']:
> +        if part['start'] == offset:
> +            return part
> +    else:
> +        raise Exception(
> +            "could not find existing partition by offset")
> +
> +
> +def _wipe_for_action(action):
> +    # If a wipe action is specified, do that.
> +    if 'wipe' in action:
> +        return action['wipe']
> +    # Existing partitions are left alone by default.
> +    if action.get('preserve', False):
> +        return None
> +    # New partitions are wiped by default apart from extended partitions, where
> +    # it would destroy the EBR.
> +    if action.get('flag') == 'extended':
> +        return None
> +    return 'superblock'
> +
> +
>  def disk_handler_v2(info, storage_config, handlers):
>      disk_handler_v1(info, storage_config, handlers)
>  
> +    part_actions = []
> +
> +    for action in storage_config.values():
> +        if action['type'] == 'partition' and action['device'] == info['id']:
> +            part_actions.append(action)
> +
> +    table_cls = {
> +        'msdos': DOSPartTable,
> +        'gpt': GPTPartTable,
> +        }.get(info.get('ptable'))
> +
> +    if table_cls is None:
> +        for action in part_actions:
> +            partition_handler_v1(action, storage_config, handlers)
> +        return
> +
> +    disk = get_path_to_storage_volume(info.get('id'), storage_config)
> +    table = table_cls()
> +    preserved_offsets = set()
> +    wipes = {}
> +
> +    sfdisk_info = block.sfdisk_info(disk)
> +    for action in part_actions:
> +        entry = table.add(action)
> +        if action.get('preserve', False):
> +            part_info = _find_part_info(sfdisk_info, entry.start)
> +            partition_verify_sfdisk(action, sfdisk_info['label'], part_info)
> +            preserved_offsets.add(entry.start)
> +        wipes[entry.start] = _wipe_for_action(action)
> +
> +    # Do a superblock wipe of any partitions that are being deleted.
> +    for kname, nr, offset, sz in block.sysfs_partition_data(disk):
> +        offset_sectors = offset // SECTOR_BYTES
> +        if offset_sectors not in preserved_offsets:
> +            block.wipe_volume(block.kname_to_path(kname), 'superblock')
> +
> +    table.apply(disk)
> +
> +    # Wipe the new partitions as needed.
> +    for kname, number, offset, size in block.sysfs_partition_data(disk):
> +        offset_sectors = offset // SECTOR_BYTES
> +        mode = wipes[offset_sectors]
> +        if mode is not None:
> +            block.wipe_volume(block.kname_to_path(kname), mode)
> +
>  
>  def partition_handler_v2(info, storage_config, handlers):
> -    partition_handler_v1(info, storage_config, handlers)
> +    pass
>  
>  
>  # vi: ts=4 expandtab syntax=python


-- 
https://code.launchpad.net/~mwhudson/curtin/+git/curtin/+merge/414079
Your team curtin developers is requested to review the proposed merge of ~mwhudson/curtin:v2-partition-ops into curtin:master.



References