kiwi.storage.disk

  1# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
  2#
  3# This file is part of kiwi.
  4#
  5# kiwi is free software: you can redistribute it and/or modify
  6# it under the terms of the GNU General Public License as published by
  7# the Free Software Foundation, either version 3 of the License, or
  8# (at your option) any later version.
  9#
 10# kiwi is distributed in the hope that it will be useful,
 11# but WITHOUT ANY WARRANTY; without even the implied warranty of
 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13# GNU General Public License for more details.
 14#
 15# You should have received a copy of the GNU General Public License
 16# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
 17#
 18import os
 19import logging
 20from collections import OrderedDict
 21from typing import (
 22    Dict, NamedTuple, Tuple, Optional
 23)
 24
 25# project
 26from kiwi.defaults import Defaults
 27from kiwi.utils.temporary import Temporary
 28from kiwi.command import Command
 29from kiwi.storage.device_provider import DeviceProvider
 30from kiwi.storage.mapped_device import MappedDevice
 31from kiwi.partitioner import Partitioner
 32from kiwi.runtime_config import RuntimeConfig
 33from kiwi.exceptions import (
 34    KiwiCustomPartitionConflictError,
 35    KiwiError
 36)
 37
 38
 39class ptable_entry_type(NamedTuple):
 40    mbsize: int
 41    clone: int
 42    partition_name: str
 43    partition_type: str
 44    partition_id: Optional[int]
 45    mountpoint: str
 46    filesystem: str
 47    label: str
 48
 49
 50log = logging.getLogger('kiwi')
 51
 52
 53class Disk(DeviceProvider):
 54    """
 55    **Implements storage disk and partition table setup**
 56    """
 57    def __init__(
 58        self, table_type: str, storage_provider: DeviceProvider,
 59        start_sector: int = None, extended_layout: bool = False
 60    ):
 61        """
 62        Construct a new Disk layout object
 63
 64        :param string table_type: Partition table type name
 65        :param object storage_provider:
 66            Instance of class based on DeviceProvider
 67        :param int start_sector: sector number
 68        :param bool extended_layout:
 69            If set to true and on msdos table type when creating
 70            more than 4 partitions, this will cause the fourth
 71            partition to be an extended partition and all following
 72            partitions will be placed as logical partitions inside
 73            of that extended partition
 74        """
 75        self.partition_mapper = RuntimeConfig().get_mapper_tool()
 76        #: the underlaying device provider
 77        self.storage_provider = storage_provider
 78
 79        #: list of protected map ids. If used in a custom partitions
 80        #: setup this will lead to a raise conditition in order to
 81        #: avoid conflicts with the existing partition layout and its
 82        #: customizaton capabilities
 83        self.protected_map_ids = [
 84            'root',
 85            'readonly',
 86            'boot',
 87            'prep',
 88            'spare',
 89            'swap',
 90            'efi_csm',
 91            'efi'
 92        ]
 93
 94        #: Unified partition UUIDs according to systemd
 95        self.gUID = self.get_discoverable_partition_ids()
 96
 97        self.partition_map: Dict[str, str] = {}
 98        self.public_partition_id_map: Dict[str, str] = {}
 99        self.partition_id_map: Dict[str, str] = {}
100        self.is_mapped = False
101
102        self.partitioner = Partitioner.new(
103            table_type, storage_provider, start_sector, extended_layout
104        )
105
106        self.table_type = table_type
107
108    def __enter__(self):
109        return self
110
111    def get_device(self) -> Dict[str, MappedDevice]:
112        """
113        Names of partition devices
114
115        Note that the mapping requires an explicit map() call
116
117        :return: instances of MappedDevice
118
119        :rtype: dict
120        """
121        device_map = {}
122        for partition_name, device_node in list(self.partition_map.items()):
123            device_map[partition_name] = MappedDevice(
124                device=device_node, device_provider=self
125            )
126        return device_map
127
128    def is_loop(self) -> bool:
129        """
130        Check if storage provider is loop based
131
132        The information is taken from the storage provider. If
133        the storage provider is loop based the disk is it too
134
135        :return: True or False
136
137        :rtype: bool
138        """
139        return self.storage_provider.is_loop()
140
141    def create_custom_partitions(
142        self, table_entries: Dict[str, ptable_entry_type]
143    ) -> None:
144        """
145        Create partitions from custom data set
146
147        .. code:: python
148
149           table_entries = {
150               map_name: ptable_entry_type
151           }
152
153        :param dict table: partition table spec
154        """
155        for map_name in table_entries:
156            if map_name in self.protected_map_ids:
157                raise KiwiCustomPartitionConflictError(
158                    f'Cannot use reserved table entry name: {map_name!r}'
159                )
160            entry = table_entries[map_name]
161            if entry.clone:
162                self._create_clones(
163                    map_name, entry.clone, entry.partition_type,
164                    format(entry.mbsize), entry.partition_id
165                )
166            id_name = f'kiwi_{map_name.title()}Part'
167            self.partitioner.create(
168                name=entry.partition_name,
169                mbsize=entry.mbsize,
170                type_name=entry.partition_type,
171                partition_id=entry.partition_id
172            )
173            self._add_to_map(map_name)
174            self._add_to_public_id_map(id_name)
175            part_uuid = self.gUID.get(entry.partition_name)
176            if part_uuid:
177                self.partitioner.set_uuid(
178                    self.partition_id_map[map_name], part_uuid
179                )
180
181    def create_root_partition(
182        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
183    ):
184        """
185        Create root partition
186
187        Populates kiwi_RootPart(id) and kiwi_BootPart(id) if no extra
188        boot partition is requested
189
190        :param str mbsize: partition size string
191        :param int clone: create [clone] cop(y/ies) of the root partition
192        :param int partition_id:
193            If provided, use this exact partition ID
194            instead of auto-incrementing. When cloned, the clone
195            ID is calculated from the given partition_id
196        """
197        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
198        if clone:
199            self._create_clones(
200                'root', clone, 't.linux', mbsize_clone, partition_id
201            )
202        self.partitioner.create(
203            name='p.lxroot',
204            mbsize=mbsize,
205            type_name='t.linux',
206            partition_id=partition_id
207        )
208        self._add_to_map('root')
209        self._add_to_public_id_map('kiwi_RootPart')
210        if 'kiwi_ROPart' in self.public_partition_id_map:
211            self._add_to_public_id_map('kiwi_RWPart')
212        if 'kiwi_BootPart' not in self.public_partition_id_map:
213            self._add_to_public_id_map('kiwi_BootPart')
214        root_uuid = self.gUID.get('root')
215        if root_uuid:
216            self.partitioner.set_uuid(
217                self.partition_id_map['root'], root_uuid
218            )
219
220    def create_root_lvm_partition(
221        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
222    ):
223        """
224        Create root partition for use with LVM
225
226        Populates kiwi_RootPart(id)
227
228        :param str mbsize: partition size string
229        :param int clone: create [clone] cop(y/ies) of the lvm roo partition
230        :param int partition_id:
231            If provided, use this exact partition ID
232            instead of auto-incrementing. When cloned, the clone
233            ID is calculated from the given partition_id
234        """
235        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
236        if clone:
237            self._create_clones(
238                'root', clone, 't.lvm', mbsize_clone, partition_id
239            )
240        self.partitioner.create(
241            name='p.lxlvm',
242            mbsize=mbsize,
243            type_name='t.lvm',
244            partition_id=partition_id
245        )
246        self._add_to_map('root')
247        self._add_to_public_id_map('kiwi_RootPart')
248        root_uuid = self.gUID.get('root')
249        if root_uuid:
250            self.partitioner.set_uuid(
251                self.partition_id_map['root'], root_uuid
252            )
253
254    def create_root_raid_partition(
255        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
256    ):
257        """
258        Create root partition for use with MD Raid
259
260        Populates kiwi_RootPart(id) and kiwi_RaidPart(id) as well
261        as the default raid device node at boot time which is
262        configured to be kiwi_RaidDev(/dev/mdX)
263
264        :param str mbsize: partition size string
265        :param int clone: create [clone] cop(y/ies) of the raid root partition
266        :param int partition_id:
267            If provided, use this exact partition ID
268            instead of auto-incrementing. When cloned, the clone
269            ID is calculated from the given partition_id
270        """
271        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
272        if clone:
273            self._create_clones(
274                'root', clone, 't.raid', mbsize_clone, partition_id
275            )
276        self.partitioner.create(
277            name='p.lxraid',
278            mbsize=mbsize,
279            type_name='t.raid',
280            partition_id=partition_id
281        )
282        self._add_to_map('root')
283        self._add_to_public_id_map('kiwi_RootPart')
284        self._add_to_public_id_map('kiwi_RaidPart')
285        root_uuid = self.gUID.get('root')
286        if root_uuid:
287            self.partitioner.set_uuid(
288                self.partition_id_map['root'], root_uuid
289            )
290
291    def create_root_readonly_partition(
292        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
293    ):
294        """
295        Create root readonly partition for use with overlayfs
296
297        Populates kiwi_ReadOnlyPart(id), the partition is meant to
298        contain a squashfs readonly filesystem. The partition size
299        should be the size of the squashfs filesystem in order to
300        avoid wasting disk space
301
302        :param str mbsize: partition size string
303        :param int clone: create [clone] cop(y/ies) of the ro root partition
304        :param int partition_id:
305            If provided, use this exact partition ID
306            instead of auto-incrementing. When cloned, the clone
307            ID is calculated from the given partition_id
308        """
309        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
310        if clone:
311            self._create_clones(
312                'root', clone, 't.linux', mbsize_clone, partition_id
313            )
314        self.partitioner.create(
315            name='p.lxreadonly',
316            mbsize=mbsize,
317            type_name='t.linux',
318            partition_id=partition_id
319        )
320        self._add_to_map('readonly')
321        self._add_to_public_id_map('kiwi_ROPart')
322        root_uuid = self.gUID.get('root')
323        if root_uuid:
324            self.partitioner.set_uuid(
325                self.partition_id_map['readonly'], root_uuid
326            )
327
328    def create_boot_partition(
329        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
330    ):
331        """
332        Create boot partition
333
334        Populates kiwi_BootPart(id) and optional kiwi_BootPartClone(id)
335
336        :param str mbsize: partition size string
337        :param int clone: create [clone] cop(y/ies) of the boot partition
338        :param int partition_id:
339            If provided, use this exact partition ID
340            instead of auto-incrementing. When cloned, the clone
341            ID is calculated from the given partition_id
342        """
343        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
344        if clone:
345            self._create_clones(
346                'boot', clone, 't.linux', mbsize_clone, partition_id
347            )
348        self.partitioner.create(
349            name='p.lxboot',
350            mbsize=mbsize,
351            type_name='t.linux',
352            partition_id=partition_id
353        )
354        self._add_to_map('boot')
355        self._add_to_public_id_map('kiwi_BootPart')
356        boot_uuid = self.gUID.get('xbootldr')
357        if boot_uuid:
358            self.partitioner.set_uuid(
359                self.partition_id_map['boot'], boot_uuid
360            )
361
362    def create_prep_partition(
363        self, mbsize: str, partition_id: Optional[int] = None
364    ):
365        """
366        Create prep partition
367
368        Populates kiwi_PrepPart(id)
369
370        :param str mbsize: partition size string
371        :param int partition_id:
372            If provided, use this exact partition ID
373            instead of auto-incrementing.
374        """
375        (mbsize, _) = Disk._parse_size(mbsize)
376        self.partitioner.create(
377            name='p.prep',
378            mbsize=mbsize,
379            type_name='t.prep',
380            partition_id=partition_id
381        )
382        self._add_to_map('prep')
383        self._add_to_public_id_map('kiwi_PrepPart')
384
385    def create_spare_partition(
386        self, mbsize: str, partition_id: Optional[int] = None
387    ):
388        """
389        Create spare partition for custom use
390
391        Populates kiwi_SparePart(id)
392
393        :param str mbsize: partition size string
394        :param int partition_id:
395            If provided, use this exact partition ID
396            instead of auto-incrementing.
397        """
398        (mbsize, _) = Disk._parse_size(mbsize)
399        self.partitioner.create(
400            name='p.spare',
401            mbsize=mbsize,
402            type_name='t.linux',
403            partition_id=partition_id
404        )
405        self._add_to_map('spare')
406        self._add_to_public_id_map('kiwi_SparePart')
407
408    def create_swap_partition(
409        self, mbsize: str, partition_id: Optional[int] = None
410    ):
411        """
412        Create swap partition
413
414        Populates kiwi_SwapPart(id)
415
416        :param str mbsize: partition size string
417        :param int partition_id:
418            If provided, use this exact partition ID
419            instead of auto-incrementing.
420        """
421        (mbsize, _) = Disk._parse_size(mbsize)
422        self.partitioner.create(
423            name='p.swap',
424            mbsize=mbsize,
425            type_name='t.swap',
426            partition_id=partition_id
427        )
428        self._add_to_map('swap')
429        self._add_to_public_id_map('kiwi_SwapPart')
430        swap_uuid = self.gUID.get('swap')
431        if swap_uuid:
432            self.partitioner.set_uuid(
433                self.partition_id_map['swap'], swap_uuid
434            )
435
436    def create_efi_csm_partition(
437        self, mbsize: str, partition_id: Optional[int] = None
438    ):
439        """
440        Create EFI bios grub partition
441
442        Populates kiwi_BiosGrub(id)
443
444        :param str mbsize: partition size string
445        :param int partition_id:
446            If provided, use this exact partition ID
447            instead of auto-incrementing.
448        """
449        (mbsize, _) = Disk._parse_size(mbsize)
450        self.partitioner.create(
451            name='p.legacy',
452            mbsize=mbsize,
453            type_name='t.csm',
454            partition_id=partition_id
455        )
456        self._add_to_map('efi_csm')
457        self._add_to_public_id_map('kiwi_BiosGrub')
458
459    def create_efi_partition(
460        self, mbsize: str, partition_id: Optional[int] = None
461    ):
462        """
463        Create EFI partition
464
465        Populates kiwi_EfiPart(id)
466
467        :param str mbsize: partition size string
468        :param int partition_id:
469            If provided, use this exact partition ID
470            instead of auto-incrementing.
471        """
472        (mbsize, _) = Disk._parse_size(mbsize)
473        self.partitioner.create(
474            name='p.UEFI',
475            mbsize=mbsize,
476            type_name='t.efi',
477            partition_id=partition_id
478        )
479        self._add_to_map('efi')
480        self._add_to_public_id_map('kiwi_EfiPart')
481        esp_uuid = self.gUID.get('esp')
482        if esp_uuid:
483            self.partitioner.set_uuid(
484                self.partition_id_map['efi'], esp_uuid
485            )
486
487    def activate_boot_partition(self):
488        """
489        Activate boot partition
490
491        Note: not all Partitioner instances supports this
492        """
493        partition_id = None
494        if 'prep' in self.partition_id_map:
495            partition_id = self.partition_id_map['prep']
496        elif 'boot' in self.partition_id_map:
497            partition_id = self.partition_id_map['boot']
498        elif 'root' in self.partition_id_map:
499            partition_id = self.partition_id_map['root']
500
501        if partition_id:
502            self.partitioner.set_flag(partition_id, 'f.active')
503
504    def create_hybrid_mbr(self):
505        """
506        Turn partition table into a hybrid GPT/MBR table
507
508        Note: only GPT tables supports this
509        """
510        self.partitioner.set_hybrid_mbr()
511
512    def create_mbr(self):
513        """
514        Turn partition table into MBR (msdos table)
515
516        Note: only GPT tables supports this
517        """
518        self.partitioner.set_mbr()
519
520    def set_start_sector(self, start_sector: int):
521        """
522        Set start sector
523
524        Note: only effective on DOS tables
525        """
526        self.partitioner.set_start_sector(start_sector)
527
528    def wipe(self):
529        """
530        Zap (destroy) any GPT and MBR data structures if present
531        For DASD disks create a new VTOC table
532        """
533        if 'dasd' in self.table_type:
534            log.debug('Initialize DASD disk with new VTOC table')
535            fdasd_input = Temporary().new_file()
536            with open(fdasd_input.name, 'w') as vtoc:
537                vtoc.write('y\n\nw\nq\n')
538            bash_command = ' '.join(
539                [
540                    'cat', fdasd_input.name, '|',
541                    'fdasd', '-f', self.storage_provider.get_device()
542                ]
543            )
544            try:
545                Command.run(
546                    ['bash', '-c', bash_command]
547                )
548            except Exception:
549                # unfortunately fdasd reports that it can't read in the
550                # partition table which I consider a bug in fdasd. However
551                # the table was correctly created and therefore we continue.
552                # Problem is that we are not able to detect real errors
553                # with the fdasd operation at that point.
554                log.debug('potential fdasd errors were ignored')
555        else:
556            log.debug('Initialize %s disk', self.table_type)
557            Command.run(
558                [
559                    'sgdisk', '--zap-all', self.storage_provider.get_device()
560                ]
561            )
562
563    def map_partitions(self):
564        """
565        Map/Activate partitions
566
567        In order to access the partitions through a device node it is
568        required to map them if the storage provider is loop based
569        """
570        if self.storage_provider.is_loop():
571            if self.partition_mapper == 'kpartx':
572                Command.run(
573                    ['kpartx', '-s', '-a', self.storage_provider.get_device()]
574                )
575            else:
576                Command.run(
577                    ['partx', '--add', self.storage_provider.get_device()]
578                )
579            self.is_mapped = True
580        else:
581            Command.run(
582                ['partprobe', self.storage_provider.get_device()]
583            )
584
585    def get_public_partition_id_map(self) -> Dict[str, str]:
586        """
587        Populated partition name to number map
588        """
589        return OrderedDict(
590            sorted(self.public_partition_id_map.items())
591        )
592
593    def get_discoverable_partition_ids(self) -> Dict[str, str]:
594        """
595        Ask systemd for a list of standardized GUIDs for the
596        current architecture and return them in a dictionary.
597        If there is no such information available an empty
598        dictionary is returned
599
600        :return: key:value dict from systemd-id128
601
602        :rtype: dict
603        """
604        discoverable_ids = {}
605        try:
606            raw_lines = Command.run(
607                ['systemd-id128', 'show']
608            ).output.split(os.linesep)[1:]
609            for line in raw_lines:
610                if line:
611                    line = ' '.join(line.split())
612                    partition_name, uuid = line.split(' ')
613                    discoverable_ids[partition_name] = uuid
614        except KiwiError as issue:
615            log.warning(
616                f'Failed to obtain discoverable partition IDs: {issue}'
617            )
618            log.warning(
619                'Using built-in table'
620            )
621            discoverable_ids = Defaults.get_discoverable_partition_ids()
622        return discoverable_ids
623
624    def _create_clones(
625        self, name: str, clone: int, type_flag: str, mbsize: str,
626        partition_id: Optional[int] = None
627    ) -> None:
628        """
629        Create [clone] cop(y/ies) of the given partition name
630
631        The name of a clone partition uses the following name policy:
632
633        * {name}clone{id} for the partition name
634        * kiwi_{name}PartClone{id} for the kiwi map name
635
636        :param str name: basename to use for clone partition names
637        :param int clone: number of clones, >= 1
638        :param str type_flag: partition type name
639        :param str mbsize: partition size string
640        :param int partition_id:
641            If provided, use this exact partition ID to
642            calculate the clone ID with.
643        """
644        for clone_id in range(1, clone + 1):
645            if partition_id:
646                partition_id += 1
647            self.partitioner.create(
648                name=f'p.lx{name}clone{partition_id or clone_id}',
649                mbsize=mbsize,
650                type_name=type_flag,
651                partition_id=partition_id
652            )
653            self._add_to_map(f'{name}clone{partition_id or clone_id}')
654            self._add_to_public_id_map(
655                f'kiwi_{name}PartClone{partition_id or clone_id}'
656            )
657
658    @staticmethod
659    def _parse_size(value: str) -> Tuple[str, str]:
660        """
661        parse size value. This can be one of the following
662
663        * A number_string
664        * The string named: 'all_free'
665        * The string formatted as:
666              clone:{number_string_origin}:{number_string_clone}
667
668        The method returns a tuple for size and optional clone size
669        If no clone size exists both tuple values are the same
670
671        The given number_string for the size of the partition is
672        passed along to the actually used partitioner object and
673        expected to be valid there. In case invalid size information
674        is passed to the partitioner an exception will be raised
675        in the scope of the partitioner interface and the selected
676        partitioner class
677
678        :param str value: size value
679
680        :return: Tuple of strings
681
682        :rtype: tuple
683        """
684        if not format(value).startswith('clone:'):
685            return (value, value)
686        else:
687            size_list = value.split(':')
688            return (size_list[1], size_list[2])
689
690    def _add_to_public_id_map(self, name, value=None):
691        if not value:
692            value = self.partitioner.get_id()
693        self.public_partition_id_map[name] = value
694
695    def _add_to_map(self, name):
696        device_node = None
697        partition_number = format(self.partitioner.get_id())
698        if self.storage_provider.is_loop():
699            device_base = os.path.basename(self.storage_provider.get_device())
700            if self.partition_mapper == 'kpartx':
701                device_node = ''.join(
702                    ['/dev/mapper/', device_base, 'p', partition_number]
703                )
704            else:
705                device_node = ''.join(
706                    ['/dev/', device_base, 'p', partition_number]
707                )
708        else:
709            device = self.storage_provider.get_device()
710            if device[-1].isdigit():
711                device_node = ''.join(
712                    [device, 'p', partition_number]
713                )
714            else:
715                device_node = ''.join(
716                    [device, partition_number]
717                )
718        if device_node:
719            self.partition_map[name] = device_node
720            self.partition_id_map[name] = partition_number
721
722    def __exit__(self, exc_type, exc_value, traceback):
723        if self.storage_provider.is_loop() and self.is_mapped:
724            log.info('Cleaning up %s instance', type(self).__name__)
725            try:
726                if self.partition_mapper == 'kpartx':
727                    for device_node in self.partition_map.values():
728                        Command.run(['dmsetup', 'remove', device_node])
729                    Command.run(
730                        ['kpartx', '-d', self.storage_provider.get_device()]
731                    )
732                else:
733                    Command.run(
734                        ['partx', '--delete', self.storage_provider.get_device()]
735                    )
736            except Exception as issue:
737                log.error(
738                    'cleanup of partition maps on {} failed with: {}'.format(
739                        self.storage_provider.get_device(), issue
740                    )
741                )
class ptable_entry_type(typing.NamedTuple):
40class ptable_entry_type(NamedTuple):
41    mbsize: int
42    clone: int
43    partition_name: str
44    partition_type: str
45    partition_id: Optional[int]
46    mountpoint: str
47    filesystem: str
48    label: str

ptable_entry_type(mbsize, clone, partition_name, partition_type, partition_id, mountpoint, filesystem, label)

ptable_entry_type( mbsize: int, clone: int, partition_name: str, partition_type: str, partition_id: Optional[int], mountpoint: str, filesystem: str, label: str)

Create new instance of ptable_entry_type(mbsize, clone, partition_name, partition_type, partition_id, mountpoint, filesystem, label)

mbsize: int

Alias for field number 0

clone: int

Alias for field number 1

partition_name: str

Alias for field number 2

partition_type: str

Alias for field number 3

partition_id: Optional[int]

Alias for field number 4

mountpoint: str

Alias for field number 5

filesystem: str

Alias for field number 6

label: str

Alias for field number 7

log = <Logger kiwi (DEBUG)>
 54class Disk(DeviceProvider):
 55    """
 56    **Implements storage disk and partition table setup**
 57    """
 58    def __init__(
 59        self, table_type: str, storage_provider: DeviceProvider,
 60        start_sector: int = None, extended_layout: bool = False
 61    ):
 62        """
 63        Construct a new Disk layout object
 64
 65        :param string table_type: Partition table type name
 66        :param object storage_provider:
 67            Instance of class based on DeviceProvider
 68        :param int start_sector: sector number
 69        :param bool extended_layout:
 70            If set to true and on msdos table type when creating
 71            more than 4 partitions, this will cause the fourth
 72            partition to be an extended partition and all following
 73            partitions will be placed as logical partitions inside
 74            of that extended partition
 75        """
 76        self.partition_mapper = RuntimeConfig().get_mapper_tool()
 77        #: the underlaying device provider
 78        self.storage_provider = storage_provider
 79
 80        #: list of protected map ids. If used in a custom partitions
 81        #: setup this will lead to a raise conditition in order to
 82        #: avoid conflicts with the existing partition layout and its
 83        #: customizaton capabilities
 84        self.protected_map_ids = [
 85            'root',
 86            'readonly',
 87            'boot',
 88            'prep',
 89            'spare',
 90            'swap',
 91            'efi_csm',
 92            'efi'
 93        ]
 94
 95        #: Unified partition UUIDs according to systemd
 96        self.gUID = self.get_discoverable_partition_ids()
 97
 98        self.partition_map: Dict[str, str] = {}
 99        self.public_partition_id_map: Dict[str, str] = {}
100        self.partition_id_map: Dict[str, str] = {}
101        self.is_mapped = False
102
103        self.partitioner = Partitioner.new(
104            table_type, storage_provider, start_sector, extended_layout
105        )
106
107        self.table_type = table_type
108
109    def __enter__(self):
110        return self
111
112    def get_device(self) -> Dict[str, MappedDevice]:
113        """
114        Names of partition devices
115
116        Note that the mapping requires an explicit map() call
117
118        :return: instances of MappedDevice
119
120        :rtype: dict
121        """
122        device_map = {}
123        for partition_name, device_node in list(self.partition_map.items()):
124            device_map[partition_name] = MappedDevice(
125                device=device_node, device_provider=self
126            )
127        return device_map
128
129    def is_loop(self) -> bool:
130        """
131        Check if storage provider is loop based
132
133        The information is taken from the storage provider. If
134        the storage provider is loop based the disk is it too
135
136        :return: True or False
137
138        :rtype: bool
139        """
140        return self.storage_provider.is_loop()
141
142    def create_custom_partitions(
143        self, table_entries: Dict[str, ptable_entry_type]
144    ) -> None:
145        """
146        Create partitions from custom data set
147
148        .. code:: python
149
150           table_entries = {
151               map_name: ptable_entry_type
152           }
153
154        :param dict table: partition table spec
155        """
156        for map_name in table_entries:
157            if map_name in self.protected_map_ids:
158                raise KiwiCustomPartitionConflictError(
159                    f'Cannot use reserved table entry name: {map_name!r}'
160                )
161            entry = table_entries[map_name]
162            if entry.clone:
163                self._create_clones(
164                    map_name, entry.clone, entry.partition_type,
165                    format(entry.mbsize), entry.partition_id
166                )
167            id_name = f'kiwi_{map_name.title()}Part'
168            self.partitioner.create(
169                name=entry.partition_name,
170                mbsize=entry.mbsize,
171                type_name=entry.partition_type,
172                partition_id=entry.partition_id
173            )
174            self._add_to_map(map_name)
175            self._add_to_public_id_map(id_name)
176            part_uuid = self.gUID.get(entry.partition_name)
177            if part_uuid:
178                self.partitioner.set_uuid(
179                    self.partition_id_map[map_name], part_uuid
180                )
181
182    def create_root_partition(
183        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
184    ):
185        """
186        Create root partition
187
188        Populates kiwi_RootPart(id) and kiwi_BootPart(id) if no extra
189        boot partition is requested
190
191        :param str mbsize: partition size string
192        :param int clone: create [clone] cop(y/ies) of the root partition
193        :param int partition_id:
194            If provided, use this exact partition ID
195            instead of auto-incrementing. When cloned, the clone
196            ID is calculated from the given partition_id
197        """
198        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
199        if clone:
200            self._create_clones(
201                'root', clone, 't.linux', mbsize_clone, partition_id
202            )
203        self.partitioner.create(
204            name='p.lxroot',
205            mbsize=mbsize,
206            type_name='t.linux',
207            partition_id=partition_id
208        )
209        self._add_to_map('root')
210        self._add_to_public_id_map('kiwi_RootPart')
211        if 'kiwi_ROPart' in self.public_partition_id_map:
212            self._add_to_public_id_map('kiwi_RWPart')
213        if 'kiwi_BootPart' not in self.public_partition_id_map:
214            self._add_to_public_id_map('kiwi_BootPart')
215        root_uuid = self.gUID.get('root')
216        if root_uuid:
217            self.partitioner.set_uuid(
218                self.partition_id_map['root'], root_uuid
219            )
220
221    def create_root_lvm_partition(
222        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
223    ):
224        """
225        Create root partition for use with LVM
226
227        Populates kiwi_RootPart(id)
228
229        :param str mbsize: partition size string
230        :param int clone: create [clone] cop(y/ies) of the lvm roo partition
231        :param int partition_id:
232            If provided, use this exact partition ID
233            instead of auto-incrementing. When cloned, the clone
234            ID is calculated from the given partition_id
235        """
236        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
237        if clone:
238            self._create_clones(
239                'root', clone, 't.lvm', mbsize_clone, partition_id
240            )
241        self.partitioner.create(
242            name='p.lxlvm',
243            mbsize=mbsize,
244            type_name='t.lvm',
245            partition_id=partition_id
246        )
247        self._add_to_map('root')
248        self._add_to_public_id_map('kiwi_RootPart')
249        root_uuid = self.gUID.get('root')
250        if root_uuid:
251            self.partitioner.set_uuid(
252                self.partition_id_map['root'], root_uuid
253            )
254
255    def create_root_raid_partition(
256        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
257    ):
258        """
259        Create root partition for use with MD Raid
260
261        Populates kiwi_RootPart(id) and kiwi_RaidPart(id) as well
262        as the default raid device node at boot time which is
263        configured to be kiwi_RaidDev(/dev/mdX)
264
265        :param str mbsize: partition size string
266        :param int clone: create [clone] cop(y/ies) of the raid root partition
267        :param int partition_id:
268            If provided, use this exact partition ID
269            instead of auto-incrementing. When cloned, the clone
270            ID is calculated from the given partition_id
271        """
272        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
273        if clone:
274            self._create_clones(
275                'root', clone, 't.raid', mbsize_clone, partition_id
276            )
277        self.partitioner.create(
278            name='p.lxraid',
279            mbsize=mbsize,
280            type_name='t.raid',
281            partition_id=partition_id
282        )
283        self._add_to_map('root')
284        self._add_to_public_id_map('kiwi_RootPart')
285        self._add_to_public_id_map('kiwi_RaidPart')
286        root_uuid = self.gUID.get('root')
287        if root_uuid:
288            self.partitioner.set_uuid(
289                self.partition_id_map['root'], root_uuid
290            )
291
292    def create_root_readonly_partition(
293        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
294    ):
295        """
296        Create root readonly partition for use with overlayfs
297
298        Populates kiwi_ReadOnlyPart(id), the partition is meant to
299        contain a squashfs readonly filesystem. The partition size
300        should be the size of the squashfs filesystem in order to
301        avoid wasting disk space
302
303        :param str mbsize: partition size string
304        :param int clone: create [clone] cop(y/ies) of the ro root partition
305        :param int partition_id:
306            If provided, use this exact partition ID
307            instead of auto-incrementing. When cloned, the clone
308            ID is calculated from the given partition_id
309        """
310        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
311        if clone:
312            self._create_clones(
313                'root', clone, 't.linux', mbsize_clone, partition_id
314            )
315        self.partitioner.create(
316            name='p.lxreadonly',
317            mbsize=mbsize,
318            type_name='t.linux',
319            partition_id=partition_id
320        )
321        self._add_to_map('readonly')
322        self._add_to_public_id_map('kiwi_ROPart')
323        root_uuid = self.gUID.get('root')
324        if root_uuid:
325            self.partitioner.set_uuid(
326                self.partition_id_map['readonly'], root_uuid
327            )
328
329    def create_boot_partition(
330        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
331    ):
332        """
333        Create boot partition
334
335        Populates kiwi_BootPart(id) and optional kiwi_BootPartClone(id)
336
337        :param str mbsize: partition size string
338        :param int clone: create [clone] cop(y/ies) of the boot partition
339        :param int partition_id:
340            If provided, use this exact partition ID
341            instead of auto-incrementing. When cloned, the clone
342            ID is calculated from the given partition_id
343        """
344        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
345        if clone:
346            self._create_clones(
347                'boot', clone, 't.linux', mbsize_clone, partition_id
348            )
349        self.partitioner.create(
350            name='p.lxboot',
351            mbsize=mbsize,
352            type_name='t.linux',
353            partition_id=partition_id
354        )
355        self._add_to_map('boot')
356        self._add_to_public_id_map('kiwi_BootPart')
357        boot_uuid = self.gUID.get('xbootldr')
358        if boot_uuid:
359            self.partitioner.set_uuid(
360                self.partition_id_map['boot'], boot_uuid
361            )
362
363    def create_prep_partition(
364        self, mbsize: str, partition_id: Optional[int] = None
365    ):
366        """
367        Create prep partition
368
369        Populates kiwi_PrepPart(id)
370
371        :param str mbsize: partition size string
372        :param int partition_id:
373            If provided, use this exact partition ID
374            instead of auto-incrementing.
375        """
376        (mbsize, _) = Disk._parse_size(mbsize)
377        self.partitioner.create(
378            name='p.prep',
379            mbsize=mbsize,
380            type_name='t.prep',
381            partition_id=partition_id
382        )
383        self._add_to_map('prep')
384        self._add_to_public_id_map('kiwi_PrepPart')
385
386    def create_spare_partition(
387        self, mbsize: str, partition_id: Optional[int] = None
388    ):
389        """
390        Create spare partition for custom use
391
392        Populates kiwi_SparePart(id)
393
394        :param str mbsize: partition size string
395        :param int partition_id:
396            If provided, use this exact partition ID
397            instead of auto-incrementing.
398        """
399        (mbsize, _) = Disk._parse_size(mbsize)
400        self.partitioner.create(
401            name='p.spare',
402            mbsize=mbsize,
403            type_name='t.linux',
404            partition_id=partition_id
405        )
406        self._add_to_map('spare')
407        self._add_to_public_id_map('kiwi_SparePart')
408
409    def create_swap_partition(
410        self, mbsize: str, partition_id: Optional[int] = None
411    ):
412        """
413        Create swap partition
414
415        Populates kiwi_SwapPart(id)
416
417        :param str mbsize: partition size string
418        :param int partition_id:
419            If provided, use this exact partition ID
420            instead of auto-incrementing.
421        """
422        (mbsize, _) = Disk._parse_size(mbsize)
423        self.partitioner.create(
424            name='p.swap',
425            mbsize=mbsize,
426            type_name='t.swap',
427            partition_id=partition_id
428        )
429        self._add_to_map('swap')
430        self._add_to_public_id_map('kiwi_SwapPart')
431        swap_uuid = self.gUID.get('swap')
432        if swap_uuid:
433            self.partitioner.set_uuid(
434                self.partition_id_map['swap'], swap_uuid
435            )
436
437    def create_efi_csm_partition(
438        self, mbsize: str, partition_id: Optional[int] = None
439    ):
440        """
441        Create EFI bios grub partition
442
443        Populates kiwi_BiosGrub(id)
444
445        :param str mbsize: partition size string
446        :param int partition_id:
447            If provided, use this exact partition ID
448            instead of auto-incrementing.
449        """
450        (mbsize, _) = Disk._parse_size(mbsize)
451        self.partitioner.create(
452            name='p.legacy',
453            mbsize=mbsize,
454            type_name='t.csm',
455            partition_id=partition_id
456        )
457        self._add_to_map('efi_csm')
458        self._add_to_public_id_map('kiwi_BiosGrub')
459
460    def create_efi_partition(
461        self, mbsize: str, partition_id: Optional[int] = None
462    ):
463        """
464        Create EFI partition
465
466        Populates kiwi_EfiPart(id)
467
468        :param str mbsize: partition size string
469        :param int partition_id:
470            If provided, use this exact partition ID
471            instead of auto-incrementing.
472        """
473        (mbsize, _) = Disk._parse_size(mbsize)
474        self.partitioner.create(
475            name='p.UEFI',
476            mbsize=mbsize,
477            type_name='t.efi',
478            partition_id=partition_id
479        )
480        self._add_to_map('efi')
481        self._add_to_public_id_map('kiwi_EfiPart')
482        esp_uuid = self.gUID.get('esp')
483        if esp_uuid:
484            self.partitioner.set_uuid(
485                self.partition_id_map['efi'], esp_uuid
486            )
487
488    def activate_boot_partition(self):
489        """
490        Activate boot partition
491
492        Note: not all Partitioner instances supports this
493        """
494        partition_id = None
495        if 'prep' in self.partition_id_map:
496            partition_id = self.partition_id_map['prep']
497        elif 'boot' in self.partition_id_map:
498            partition_id = self.partition_id_map['boot']
499        elif 'root' in self.partition_id_map:
500            partition_id = self.partition_id_map['root']
501
502        if partition_id:
503            self.partitioner.set_flag(partition_id, 'f.active')
504
505    def create_hybrid_mbr(self):
506        """
507        Turn partition table into a hybrid GPT/MBR table
508
509        Note: only GPT tables supports this
510        """
511        self.partitioner.set_hybrid_mbr()
512
513    def create_mbr(self):
514        """
515        Turn partition table into MBR (msdos table)
516
517        Note: only GPT tables supports this
518        """
519        self.partitioner.set_mbr()
520
521    def set_start_sector(self, start_sector: int):
522        """
523        Set start sector
524
525        Note: only effective on DOS tables
526        """
527        self.partitioner.set_start_sector(start_sector)
528
529    def wipe(self):
530        """
531        Zap (destroy) any GPT and MBR data structures if present
532        For DASD disks create a new VTOC table
533        """
534        if 'dasd' in self.table_type:
535            log.debug('Initialize DASD disk with new VTOC table')
536            fdasd_input = Temporary().new_file()
537            with open(fdasd_input.name, 'w') as vtoc:
538                vtoc.write('y\n\nw\nq\n')
539            bash_command = ' '.join(
540                [
541                    'cat', fdasd_input.name, '|',
542                    'fdasd', '-f', self.storage_provider.get_device()
543                ]
544            )
545            try:
546                Command.run(
547                    ['bash', '-c', bash_command]
548                )
549            except Exception:
550                # unfortunately fdasd reports that it can't read in the
551                # partition table which I consider a bug in fdasd. However
552                # the table was correctly created and therefore we continue.
553                # Problem is that we are not able to detect real errors
554                # with the fdasd operation at that point.
555                log.debug('potential fdasd errors were ignored')
556        else:
557            log.debug('Initialize %s disk', self.table_type)
558            Command.run(
559                [
560                    'sgdisk', '--zap-all', self.storage_provider.get_device()
561                ]
562            )
563
564    def map_partitions(self):
565        """
566        Map/Activate partitions
567
568        In order to access the partitions through a device node it is
569        required to map them if the storage provider is loop based
570        """
571        if self.storage_provider.is_loop():
572            if self.partition_mapper == 'kpartx':
573                Command.run(
574                    ['kpartx', '-s', '-a', self.storage_provider.get_device()]
575                )
576            else:
577                Command.run(
578                    ['partx', '--add', self.storage_provider.get_device()]
579                )
580            self.is_mapped = True
581        else:
582            Command.run(
583                ['partprobe', self.storage_provider.get_device()]
584            )
585
586    def get_public_partition_id_map(self) -> Dict[str, str]:
587        """
588        Populated partition name to number map
589        """
590        return OrderedDict(
591            sorted(self.public_partition_id_map.items())
592        )
593
594    def get_discoverable_partition_ids(self) -> Dict[str, str]:
595        """
596        Ask systemd for a list of standardized GUIDs for the
597        current architecture and return them in a dictionary.
598        If there is no such information available an empty
599        dictionary is returned
600
601        :return: key:value dict from systemd-id128
602
603        :rtype: dict
604        """
605        discoverable_ids = {}
606        try:
607            raw_lines = Command.run(
608                ['systemd-id128', 'show']
609            ).output.split(os.linesep)[1:]
610            for line in raw_lines:
611                if line:
612                    line = ' '.join(line.split())
613                    partition_name, uuid = line.split(' ')
614                    discoverable_ids[partition_name] = uuid
615        except KiwiError as issue:
616            log.warning(
617                f'Failed to obtain discoverable partition IDs: {issue}'
618            )
619            log.warning(
620                'Using built-in table'
621            )
622            discoverable_ids = Defaults.get_discoverable_partition_ids()
623        return discoverable_ids
624
625    def _create_clones(
626        self, name: str, clone: int, type_flag: str, mbsize: str,
627        partition_id: Optional[int] = None
628    ) -> None:
629        """
630        Create [clone] cop(y/ies) of the given partition name
631
632        The name of a clone partition uses the following name policy:
633
634        * {name}clone{id} for the partition name
635        * kiwi_{name}PartClone{id} for the kiwi map name
636
637        :param str name: basename to use for clone partition names
638        :param int clone: number of clones, >= 1
639        :param str type_flag: partition type name
640        :param str mbsize: partition size string
641        :param int partition_id:
642            If provided, use this exact partition ID to
643            calculate the clone ID with.
644        """
645        for clone_id in range(1, clone + 1):
646            if partition_id:
647                partition_id += 1
648            self.partitioner.create(
649                name=f'p.lx{name}clone{partition_id or clone_id}',
650                mbsize=mbsize,
651                type_name=type_flag,
652                partition_id=partition_id
653            )
654            self._add_to_map(f'{name}clone{partition_id or clone_id}')
655            self._add_to_public_id_map(
656                f'kiwi_{name}PartClone{partition_id or clone_id}'
657            )
658
659    @staticmethod
660    def _parse_size(value: str) -> Tuple[str, str]:
661        """
662        parse size value. This can be one of the following
663
664        * A number_string
665        * The string named: 'all_free'
666        * The string formatted as:
667              clone:{number_string_origin}:{number_string_clone}
668
669        The method returns a tuple for size and optional clone size
670        If no clone size exists both tuple values are the same
671
672        The given number_string for the size of the partition is
673        passed along to the actually used partitioner object and
674        expected to be valid there. In case invalid size information
675        is passed to the partitioner an exception will be raised
676        in the scope of the partitioner interface and the selected
677        partitioner class
678
679        :param str value: size value
680
681        :return: Tuple of strings
682
683        :rtype: tuple
684        """
685        if not format(value).startswith('clone:'):
686            return (value, value)
687        else:
688            size_list = value.split(':')
689            return (size_list[1], size_list[2])
690
691    def _add_to_public_id_map(self, name, value=None):
692        if not value:
693            value = self.partitioner.get_id()
694        self.public_partition_id_map[name] = value
695
696    def _add_to_map(self, name):
697        device_node = None
698        partition_number = format(self.partitioner.get_id())
699        if self.storage_provider.is_loop():
700            device_base = os.path.basename(self.storage_provider.get_device())
701            if self.partition_mapper == 'kpartx':
702                device_node = ''.join(
703                    ['/dev/mapper/', device_base, 'p', partition_number]
704                )
705            else:
706                device_node = ''.join(
707                    ['/dev/', device_base, 'p', partition_number]
708                )
709        else:
710            device = self.storage_provider.get_device()
711            if device[-1].isdigit():
712                device_node = ''.join(
713                    [device, 'p', partition_number]
714                )
715            else:
716                device_node = ''.join(
717                    [device, partition_number]
718                )
719        if device_node:
720            self.partition_map[name] = device_node
721            self.partition_id_map[name] = partition_number
722
723    def __exit__(self, exc_type, exc_value, traceback):
724        if self.storage_provider.is_loop() and self.is_mapped:
725            log.info('Cleaning up %s instance', type(self).__name__)
726            try:
727                if self.partition_mapper == 'kpartx':
728                    for device_node in self.partition_map.values():
729                        Command.run(['dmsetup', 'remove', device_node])
730                    Command.run(
731                        ['kpartx', '-d', self.storage_provider.get_device()]
732                    )
733                else:
734                    Command.run(
735                        ['partx', '--delete', self.storage_provider.get_device()]
736                    )
737            except Exception as issue:
738                log.error(
739                    'cleanup of partition maps on {} failed with: {}'.format(
740                        self.storage_provider.get_device(), issue
741                    )
742                )

Implements storage disk and partition table setup

Disk( table_type: str, storage_provider: kiwi.storage.device_provider.DeviceProvider, start_sector: int = None, extended_layout: bool = False)
 58    def __init__(
 59        self, table_type: str, storage_provider: DeviceProvider,
 60        start_sector: int = None, extended_layout: bool = False
 61    ):
 62        """
 63        Construct a new Disk layout object
 64
 65        :param string table_type: Partition table type name
 66        :param object storage_provider:
 67            Instance of class based on DeviceProvider
 68        :param int start_sector: sector number
 69        :param bool extended_layout:
 70            If set to true and on msdos table type when creating
 71            more than 4 partitions, this will cause the fourth
 72            partition to be an extended partition and all following
 73            partitions will be placed as logical partitions inside
 74            of that extended partition
 75        """
 76        self.partition_mapper = RuntimeConfig().get_mapper_tool()
 77        #: the underlaying device provider
 78        self.storage_provider = storage_provider
 79
 80        #: list of protected map ids. If used in a custom partitions
 81        #: setup this will lead to a raise conditition in order to
 82        #: avoid conflicts with the existing partition layout and its
 83        #: customizaton capabilities
 84        self.protected_map_ids = [
 85            'root',
 86            'readonly',
 87            'boot',
 88            'prep',
 89            'spare',
 90            'swap',
 91            'efi_csm',
 92            'efi'
 93        ]
 94
 95        #: Unified partition UUIDs according to systemd
 96        self.gUID = self.get_discoverable_partition_ids()
 97
 98        self.partition_map: Dict[str, str] = {}
 99        self.public_partition_id_map: Dict[str, str] = {}
100        self.partition_id_map: Dict[str, str] = {}
101        self.is_mapped = False
102
103        self.partitioner = Partitioner.new(
104            table_type, storage_provider, start_sector, extended_layout
105        )
106
107        self.table_type = table_type

Construct a new Disk layout object

Parameters
  • string table_type: Partition table type name
  • object storage_provider: Instance of class based on DeviceProvider
  • int start_sector: sector number
  • bool extended_layout: If set to true and on msdos table type when creating more than 4 partitions, this will cause the fourth partition to be an extended partition and all following partitions will be placed as logical partitions inside of that extended partition
partition_mapper
storage_provider
protected_map_ids
gUID
partition_map: Dict[str, str]
public_partition_id_map: Dict[str, str]
partition_id_map: Dict[str, str]
is_mapped
partitioner
table_type
def get_device(self) -> Dict[str, kiwi.storage.mapped_device.MappedDevice]:
112    def get_device(self) -> Dict[str, MappedDevice]:
113        """
114        Names of partition devices
115
116        Note that the mapping requires an explicit map() call
117
118        :return: instances of MappedDevice
119
120        :rtype: dict
121        """
122        device_map = {}
123        for partition_name, device_node in list(self.partition_map.items()):
124            device_map[partition_name] = MappedDevice(
125                device=device_node, device_provider=self
126            )
127        return device_map

Names of partition devices

Note that the mapping requires an explicit map() call

Returns

instances of MappedDevice

def is_loop(self) -> bool:
129    def is_loop(self) -> bool:
130        """
131        Check if storage provider is loop based
132
133        The information is taken from the storage provider. If
134        the storage provider is loop based the disk is it too
135
136        :return: True or False
137
138        :rtype: bool
139        """
140        return self.storage_provider.is_loop()

Check if storage provider is loop based

The information is taken from the storage provider. If the storage provider is loop based the disk is it too

Returns

True or False

def create_custom_partitions( self, table_entries: Dict[str, ptable_entry_type]) -> None:
142    def create_custom_partitions(
143        self, table_entries: Dict[str, ptable_entry_type]
144    ) -> None:
145        """
146        Create partitions from custom data set
147
148        .. code:: python
149
150           table_entries = {
151               map_name: ptable_entry_type
152           }
153
154        :param dict table: partition table spec
155        """
156        for map_name in table_entries:
157            if map_name in self.protected_map_ids:
158                raise KiwiCustomPartitionConflictError(
159                    f'Cannot use reserved table entry name: {map_name!r}'
160                )
161            entry = table_entries[map_name]
162            if entry.clone:
163                self._create_clones(
164                    map_name, entry.clone, entry.partition_type,
165                    format(entry.mbsize), entry.partition_id
166                )
167            id_name = f'kiwi_{map_name.title()}Part'
168            self.partitioner.create(
169                name=entry.partition_name,
170                mbsize=entry.mbsize,
171                type_name=entry.partition_type,
172                partition_id=entry.partition_id
173            )
174            self._add_to_map(map_name)
175            self._add_to_public_id_map(id_name)
176            part_uuid = self.gUID.get(entry.partition_name)
177            if part_uuid:
178                self.partitioner.set_uuid(
179                    self.partition_id_map[map_name], part_uuid
180                )

Create partitions from custom data set

.. code:: python

table_entries = { map_name: ptable_entry_type }

Parameters
  • dict table: partition table spec
def create_root_partition( self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None):
182    def create_root_partition(
183        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
184    ):
185        """
186        Create root partition
187
188        Populates kiwi_RootPart(id) and kiwi_BootPart(id) if no extra
189        boot partition is requested
190
191        :param str mbsize: partition size string
192        :param int clone: create [clone] cop(y/ies) of the root partition
193        :param int partition_id:
194            If provided, use this exact partition ID
195            instead of auto-incrementing. When cloned, the clone
196            ID is calculated from the given partition_id
197        """
198        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
199        if clone:
200            self._create_clones(
201                'root', clone, 't.linux', mbsize_clone, partition_id
202            )
203        self.partitioner.create(
204            name='p.lxroot',
205            mbsize=mbsize,
206            type_name='t.linux',
207            partition_id=partition_id
208        )
209        self._add_to_map('root')
210        self._add_to_public_id_map('kiwi_RootPart')
211        if 'kiwi_ROPart' in self.public_partition_id_map:
212            self._add_to_public_id_map('kiwi_RWPart')
213        if 'kiwi_BootPart' not in self.public_partition_id_map:
214            self._add_to_public_id_map('kiwi_BootPart')
215        root_uuid = self.gUID.get('root')
216        if root_uuid:
217            self.partitioner.set_uuid(
218                self.partition_id_map['root'], root_uuid
219            )

Create root partition

Populates kiwi_RootPart(id) and kiwi_BootPart(id) if no extra boot partition is requested

Parameters
  • str mbsize: partition size string
  • int clone: create [clone] cop(y/ies) of the root partition
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing. When cloned, the clone ID is calculated from the given partition_id
def create_root_lvm_partition( self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None):
221    def create_root_lvm_partition(
222        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
223    ):
224        """
225        Create root partition for use with LVM
226
227        Populates kiwi_RootPart(id)
228
229        :param str mbsize: partition size string
230        :param int clone: create [clone] cop(y/ies) of the lvm roo partition
231        :param int partition_id:
232            If provided, use this exact partition ID
233            instead of auto-incrementing. When cloned, the clone
234            ID is calculated from the given partition_id
235        """
236        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
237        if clone:
238            self._create_clones(
239                'root', clone, 't.lvm', mbsize_clone, partition_id
240            )
241        self.partitioner.create(
242            name='p.lxlvm',
243            mbsize=mbsize,
244            type_name='t.lvm',
245            partition_id=partition_id
246        )
247        self._add_to_map('root')
248        self._add_to_public_id_map('kiwi_RootPart')
249        root_uuid = self.gUID.get('root')
250        if root_uuid:
251            self.partitioner.set_uuid(
252                self.partition_id_map['root'], root_uuid
253            )

Create root partition for use with LVM

Populates kiwi_RootPart(id)

Parameters
  • str mbsize: partition size string
  • int clone: create [clone] cop(y/ies) of the lvm roo partition
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing. When cloned, the clone ID is calculated from the given partition_id
def create_root_raid_partition( self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None):
255    def create_root_raid_partition(
256        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
257    ):
258        """
259        Create root partition for use with MD Raid
260
261        Populates kiwi_RootPart(id) and kiwi_RaidPart(id) as well
262        as the default raid device node at boot time which is
263        configured to be kiwi_RaidDev(/dev/mdX)
264
265        :param str mbsize: partition size string
266        :param int clone: create [clone] cop(y/ies) of the raid root partition
267        :param int partition_id:
268            If provided, use this exact partition ID
269            instead of auto-incrementing. When cloned, the clone
270            ID is calculated from the given partition_id
271        """
272        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
273        if clone:
274            self._create_clones(
275                'root', clone, 't.raid', mbsize_clone, partition_id
276            )
277        self.partitioner.create(
278            name='p.lxraid',
279            mbsize=mbsize,
280            type_name='t.raid',
281            partition_id=partition_id
282        )
283        self._add_to_map('root')
284        self._add_to_public_id_map('kiwi_RootPart')
285        self._add_to_public_id_map('kiwi_RaidPart')
286        root_uuid = self.gUID.get('root')
287        if root_uuid:
288            self.partitioner.set_uuid(
289                self.partition_id_map['root'], root_uuid
290            )

Create root partition for use with MD Raid

Populates kiwi_RootPart(id) and kiwi_RaidPart(id) as well as the default raid device node at boot time which is configured to be kiwi_RaidDev(/dev/mdX)

Parameters
  • str mbsize: partition size string
  • int clone: create [clone] cop(y/ies) of the raid root partition
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing. When cloned, the clone ID is calculated from the given partition_id
def create_root_readonly_partition( self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None):
292    def create_root_readonly_partition(
293        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
294    ):
295        """
296        Create root readonly partition for use with overlayfs
297
298        Populates kiwi_ReadOnlyPart(id), the partition is meant to
299        contain a squashfs readonly filesystem. The partition size
300        should be the size of the squashfs filesystem in order to
301        avoid wasting disk space
302
303        :param str mbsize: partition size string
304        :param int clone: create [clone] cop(y/ies) of the ro root partition
305        :param int partition_id:
306            If provided, use this exact partition ID
307            instead of auto-incrementing. When cloned, the clone
308            ID is calculated from the given partition_id
309        """
310        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
311        if clone:
312            self._create_clones(
313                'root', clone, 't.linux', mbsize_clone, partition_id
314            )
315        self.partitioner.create(
316            name='p.lxreadonly',
317            mbsize=mbsize,
318            type_name='t.linux',
319            partition_id=partition_id
320        )
321        self._add_to_map('readonly')
322        self._add_to_public_id_map('kiwi_ROPart')
323        root_uuid = self.gUID.get('root')
324        if root_uuid:
325            self.partitioner.set_uuid(
326                self.partition_id_map['readonly'], root_uuid
327            )

Create root readonly partition for use with overlayfs

Populates kiwi_ReadOnlyPart(id), the partition is meant to contain a squashfs readonly filesystem. The partition size should be the size of the squashfs filesystem in order to avoid wasting disk space

Parameters
  • str mbsize: partition size string
  • int clone: create [clone] cop(y/ies) of the ro root partition
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing. When cloned, the clone ID is calculated from the given partition_id
def create_boot_partition( self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None):
329    def create_boot_partition(
330        self, mbsize: str, clone: int = 0, partition_id: Optional[int] = None
331    ):
332        """
333        Create boot partition
334
335        Populates kiwi_BootPart(id) and optional kiwi_BootPartClone(id)
336
337        :param str mbsize: partition size string
338        :param int clone: create [clone] cop(y/ies) of the boot partition
339        :param int partition_id:
340            If provided, use this exact partition ID
341            instead of auto-incrementing. When cloned, the clone
342            ID is calculated from the given partition_id
343        """
344        (mbsize, mbsize_clone) = Disk._parse_size(mbsize)
345        if clone:
346            self._create_clones(
347                'boot', clone, 't.linux', mbsize_clone, partition_id
348            )
349        self.partitioner.create(
350            name='p.lxboot',
351            mbsize=mbsize,
352            type_name='t.linux',
353            partition_id=partition_id
354        )
355        self._add_to_map('boot')
356        self._add_to_public_id_map('kiwi_BootPart')
357        boot_uuid = self.gUID.get('xbootldr')
358        if boot_uuid:
359            self.partitioner.set_uuid(
360                self.partition_id_map['boot'], boot_uuid
361            )

Create boot partition

Populates kiwi_BootPart(id) and optional kiwi_BootPartClone(id)

Parameters
  • str mbsize: partition size string
  • int clone: create [clone] cop(y/ies) of the boot partition
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing. When cloned, the clone ID is calculated from the given partition_id
def create_prep_partition(self, mbsize: str, partition_id: Optional[int] = None):
363    def create_prep_partition(
364        self, mbsize: str, partition_id: Optional[int] = None
365    ):
366        """
367        Create prep partition
368
369        Populates kiwi_PrepPart(id)
370
371        :param str mbsize: partition size string
372        :param int partition_id:
373            If provided, use this exact partition ID
374            instead of auto-incrementing.
375        """
376        (mbsize, _) = Disk._parse_size(mbsize)
377        self.partitioner.create(
378            name='p.prep',
379            mbsize=mbsize,
380            type_name='t.prep',
381            partition_id=partition_id
382        )
383        self._add_to_map('prep')
384        self._add_to_public_id_map('kiwi_PrepPart')

Create prep partition

Populates kiwi_PrepPart(id)

Parameters
  • str mbsize: partition size string
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing.
def create_spare_partition(self, mbsize: str, partition_id: Optional[int] = None):
386    def create_spare_partition(
387        self, mbsize: str, partition_id: Optional[int] = None
388    ):
389        """
390        Create spare partition for custom use
391
392        Populates kiwi_SparePart(id)
393
394        :param str mbsize: partition size string
395        :param int partition_id:
396            If provided, use this exact partition ID
397            instead of auto-incrementing.
398        """
399        (mbsize, _) = Disk._parse_size(mbsize)
400        self.partitioner.create(
401            name='p.spare',
402            mbsize=mbsize,
403            type_name='t.linux',
404            partition_id=partition_id
405        )
406        self._add_to_map('spare')
407        self._add_to_public_id_map('kiwi_SparePart')

Create spare partition for custom use

Populates kiwi_SparePart(id)

Parameters
  • str mbsize: partition size string
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing.
def create_swap_partition(self, mbsize: str, partition_id: Optional[int] = None):
409    def create_swap_partition(
410        self, mbsize: str, partition_id: Optional[int] = None
411    ):
412        """
413        Create swap partition
414
415        Populates kiwi_SwapPart(id)
416
417        :param str mbsize: partition size string
418        :param int partition_id:
419            If provided, use this exact partition ID
420            instead of auto-incrementing.
421        """
422        (mbsize, _) = Disk._parse_size(mbsize)
423        self.partitioner.create(
424            name='p.swap',
425            mbsize=mbsize,
426            type_name='t.swap',
427            partition_id=partition_id
428        )
429        self._add_to_map('swap')
430        self._add_to_public_id_map('kiwi_SwapPart')
431        swap_uuid = self.gUID.get('swap')
432        if swap_uuid:
433            self.partitioner.set_uuid(
434                self.partition_id_map['swap'], swap_uuid
435            )

Create swap partition

Populates kiwi_SwapPart(id)

Parameters
  • str mbsize: partition size string
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing.
def create_efi_csm_partition(self, mbsize: str, partition_id: Optional[int] = None):
437    def create_efi_csm_partition(
438        self, mbsize: str, partition_id: Optional[int] = None
439    ):
440        """
441        Create EFI bios grub partition
442
443        Populates kiwi_BiosGrub(id)
444
445        :param str mbsize: partition size string
446        :param int partition_id:
447            If provided, use this exact partition ID
448            instead of auto-incrementing.
449        """
450        (mbsize, _) = Disk._parse_size(mbsize)
451        self.partitioner.create(
452            name='p.legacy',
453            mbsize=mbsize,
454            type_name='t.csm',
455            partition_id=partition_id
456        )
457        self._add_to_map('efi_csm')
458        self._add_to_public_id_map('kiwi_BiosGrub')

Create EFI bios grub partition

Populates kiwi_BiosGrub(id)

Parameters
  • str mbsize: partition size string
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing.
def create_efi_partition(self, mbsize: str, partition_id: Optional[int] = None):
460    def create_efi_partition(
461        self, mbsize: str, partition_id: Optional[int] = None
462    ):
463        """
464        Create EFI partition
465
466        Populates kiwi_EfiPart(id)
467
468        :param str mbsize: partition size string
469        :param int partition_id:
470            If provided, use this exact partition ID
471            instead of auto-incrementing.
472        """
473        (mbsize, _) = Disk._parse_size(mbsize)
474        self.partitioner.create(
475            name='p.UEFI',
476            mbsize=mbsize,
477            type_name='t.efi',
478            partition_id=partition_id
479        )
480        self._add_to_map('efi')
481        self._add_to_public_id_map('kiwi_EfiPart')
482        esp_uuid = self.gUID.get('esp')
483        if esp_uuid:
484            self.partitioner.set_uuid(
485                self.partition_id_map['efi'], esp_uuid
486            )

Create EFI partition

Populates kiwi_EfiPart(id)

Parameters
  • str mbsize: partition size string
  • int partition_id: If provided, use this exact partition ID instead of auto-incrementing.
def activate_boot_partition(self):
488    def activate_boot_partition(self):
489        """
490        Activate boot partition
491
492        Note: not all Partitioner instances supports this
493        """
494        partition_id = None
495        if 'prep' in self.partition_id_map:
496            partition_id = self.partition_id_map['prep']
497        elif 'boot' in self.partition_id_map:
498            partition_id = self.partition_id_map['boot']
499        elif 'root' in self.partition_id_map:
500            partition_id = self.partition_id_map['root']
501
502        if partition_id:
503            self.partitioner.set_flag(partition_id, 'f.active')

Activate boot partition

Note: not all Partitioner instances supports this

def create_hybrid_mbr(self):
505    def create_hybrid_mbr(self):
506        """
507        Turn partition table into a hybrid GPT/MBR table
508
509        Note: only GPT tables supports this
510        """
511        self.partitioner.set_hybrid_mbr()

Turn partition table into a hybrid GPT/MBR table

Note: only GPT tables supports this

def create_mbr(self):
513    def create_mbr(self):
514        """
515        Turn partition table into MBR (msdos table)
516
517        Note: only GPT tables supports this
518        """
519        self.partitioner.set_mbr()

Turn partition table into MBR (msdos table)

Note: only GPT tables supports this

def set_start_sector(self, start_sector: int):
521    def set_start_sector(self, start_sector: int):
522        """
523        Set start sector
524
525        Note: only effective on DOS tables
526        """
527        self.partitioner.set_start_sector(start_sector)

Set start sector

Note: only effective on DOS tables

def wipe(self):
529    def wipe(self):
530        """
531        Zap (destroy) any GPT and MBR data structures if present
532        For DASD disks create a new VTOC table
533        """
534        if 'dasd' in self.table_type:
535            log.debug('Initialize DASD disk with new VTOC table')
536            fdasd_input = Temporary().new_file()
537            with open(fdasd_input.name, 'w') as vtoc:
538                vtoc.write('y\n\nw\nq\n')
539            bash_command = ' '.join(
540                [
541                    'cat', fdasd_input.name, '|',
542                    'fdasd', '-f', self.storage_provider.get_device()
543                ]
544            )
545            try:
546                Command.run(
547                    ['bash', '-c', bash_command]
548                )
549            except Exception:
550                # unfortunately fdasd reports that it can't read in the
551                # partition table which I consider a bug in fdasd. However
552                # the table was correctly created and therefore we continue.
553                # Problem is that we are not able to detect real errors
554                # with the fdasd operation at that point.
555                log.debug('potential fdasd errors were ignored')
556        else:
557            log.debug('Initialize %s disk', self.table_type)
558            Command.run(
559                [
560                    'sgdisk', '--zap-all', self.storage_provider.get_device()
561                ]
562            )

Zap (destroy) any GPT and MBR data structures if present For DASD disks create a new VTOC table

def map_partitions(self):
564    def map_partitions(self):
565        """
566        Map/Activate partitions
567
568        In order to access the partitions through a device node it is
569        required to map them if the storage provider is loop based
570        """
571        if self.storage_provider.is_loop():
572            if self.partition_mapper == 'kpartx':
573                Command.run(
574                    ['kpartx', '-s', '-a', self.storage_provider.get_device()]
575                )
576            else:
577                Command.run(
578                    ['partx', '--add', self.storage_provider.get_device()]
579                )
580            self.is_mapped = True
581        else:
582            Command.run(
583                ['partprobe', self.storage_provider.get_device()]
584            )

Map/Activate partitions

In order to access the partitions through a device node it is required to map them if the storage provider is loop based

def get_public_partition_id_map(self) -> Dict[str, str]:
586    def get_public_partition_id_map(self) -> Dict[str, str]:
587        """
588        Populated partition name to number map
589        """
590        return OrderedDict(
591            sorted(self.public_partition_id_map.items())
592        )

Populated partition name to number map

def get_discoverable_partition_ids(self) -> Dict[str, str]:
594    def get_discoverable_partition_ids(self) -> Dict[str, str]:
595        """
596        Ask systemd for a list of standardized GUIDs for the
597        current architecture and return them in a dictionary.
598        If there is no such information available an empty
599        dictionary is returned
600
601        :return: key:value dict from systemd-id128
602
603        :rtype: dict
604        """
605        discoverable_ids = {}
606        try:
607            raw_lines = Command.run(
608                ['systemd-id128', 'show']
609            ).output.split(os.linesep)[1:]
610            for line in raw_lines:
611                if line:
612                    line = ' '.join(line.split())
613                    partition_name, uuid = line.split(' ')
614                    discoverable_ids[partition_name] = uuid
615        except KiwiError as issue:
616            log.warning(
617                f'Failed to obtain discoverable partition IDs: {issue}'
618            )
619            log.warning(
620                'Using built-in table'
621            )
622            discoverable_ids = Defaults.get_discoverable_partition_ids()
623        return discoverable_ids

Ask systemd for a list of standardized GUIDs for the current architecture and return them in a dictionary. If there is no such information available an empty dictionary is returned

Returns

key:value dict from systemd-id128