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

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                )
181                self.partitioner.set_flag(
182                    entry.partition_id, entry.partition_type
183                )

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

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

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

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

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

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):
368    def create_prep_partition(
369        self, mbsize: str, partition_id: Optional[int] = None
370    ):
371        """
372        Create prep partition
373
374        Populates kiwi_PrepPart(id)
375
376        :param str mbsize: partition size string
377        :param int partition_id:
378            If provided, use this exact partition ID
379            instead of auto-incrementing.
380        """
381        (mbsize, _) = Disk._parse_size(mbsize)
382        self.partitioner.create(
383            name='p.prep',
384            mbsize=mbsize,
385            type_name='t.prep',
386            partition_id=partition_id
387        )
388        self._add_to_map('prep')
389        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):
391    def create_spare_partition(
392        self, mbsize: str, partition_id: Optional[int] = None
393    ):
394        """
395        Create spare partition for custom use
396
397        Populates kiwi_SparePart(id)
398
399        :param str mbsize: partition size string
400        :param int partition_id:
401            If provided, use this exact partition ID
402            instead of auto-incrementing.
403        """
404        (mbsize, _) = Disk._parse_size(mbsize)
405        self.partitioner.create(
406            name='p.spare',
407            mbsize=mbsize,
408            type_name='t.linux',
409            partition_id=partition_id
410        )
411        self._add_to_map('spare')
412        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):
414    def create_swap_partition(
415        self, mbsize: str, partition_id: Optional[int] = None
416    ):
417        """
418        Create swap partition
419
420        Populates kiwi_SwapPart(id)
421
422        :param str mbsize: partition size string
423        :param int partition_id:
424            If provided, use this exact partition ID
425            instead of auto-incrementing.
426        """
427        (mbsize, _) = Disk._parse_size(mbsize)
428        self.partitioner.create(
429            name='p.swap',
430            mbsize=mbsize,
431            type_name='t.swap',
432            partition_id=partition_id
433        )
434        self._add_to_map('swap')
435        self._add_to_public_id_map('kiwi_SwapPart')
436        swap_uuid = self.gUID.get('swap')
437        if swap_uuid:
438            self.partitioner.set_uuid(
439                self.partition_id_map['swap'], swap_uuid
440            )
441            self.partitioner.set_flag(partition_id, 't.swap')

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):
443    def create_efi_csm_partition(
444        self, mbsize: str, partition_id: Optional[int] = None
445    ):
446        """
447        Create EFI bios grub partition
448
449        Populates kiwi_BiosGrub(id)
450
451        :param str mbsize: partition size string
452        :param int partition_id:
453            If provided, use this exact partition ID
454            instead of auto-incrementing.
455        """
456        (mbsize, _) = Disk._parse_size(mbsize)
457        self.partitioner.create(
458            name='p.legacy',
459            mbsize=mbsize,
460            type_name='t.csm',
461            partition_id=partition_id
462        )
463        self._add_to_map('efi_csm')
464        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):
466    def create_efi_partition(
467        self, mbsize: str, partition_id: Optional[int] = None
468    ):
469        """
470        Create EFI partition
471
472        Populates kiwi_EfiPart(id)
473
474        :param str mbsize: partition size string
475        :param int partition_id:
476            If provided, use this exact partition ID
477            instead of auto-incrementing.
478        """
479        (mbsize, _) = Disk._parse_size(mbsize)
480        self.partitioner.create(
481            name='p.UEFI',
482            mbsize=mbsize,
483            type_name='t.efi',
484            partition_id=partition_id
485        )
486        self._add_to_map('efi')
487        self._add_to_public_id_map('kiwi_EfiPart')
488        esp_uuid = self.gUID.get('esp')
489        if esp_uuid:
490            self.partitioner.set_uuid(
491                self.partition_id_map['efi'], esp_uuid
492            )
493            self.partitioner.set_flag(partition_id, 't.efi')

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):
495    def activate_boot_partition(self):
496        """
497        Activate boot partition
498
499        Note: not all Partitioner instances supports this
500        """
501        partition_id = None
502        if 'prep' in self.partition_id_map:
503            partition_id = self.partition_id_map['prep']
504        elif 'boot' in self.partition_id_map:
505            partition_id = self.partition_id_map['boot']
506        elif 'root' in self.partition_id_map:
507            partition_id = self.partition_id_map['root']
508
509        if partition_id:
510            self.partitioner.set_flag(partition_id, 'f.active')

Activate boot partition

Note: not all Partitioner instances supports this

def create_hybrid_mbr(self):
512    def create_hybrid_mbr(self):
513        """
514        Turn partition table into a hybrid GPT/MBR table
515
516        Note: only GPT tables supports this
517        """
518        self.partitioner.set_hybrid_mbr()

Turn partition table into a hybrid GPT/MBR table

Note: only GPT tables supports this

def create_mbr(self):
520    def create_mbr(self):
521        """
522        Turn partition table into MBR (msdos table)
523
524        Note: only GPT tables supports this
525        """
526        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):
528    def set_start_sector(self, start_sector: int):
529        """
530        Set start sector
531
532        Note: only effective on DOS tables
533        """
534        self.partitioner.set_start_sector(start_sector)

Set start sector

Note: only effective on DOS tables

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

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

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

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]:
593    def get_public_partition_id_map(self) -> Dict[str, str]:
594        """
595        Populated partition name to number map
596        """
597        return OrderedDict(
598            sorted(self.public_partition_id_map.items())
599        )

Populated partition name to number map

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