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 )
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)
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
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
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
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
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
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
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
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
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
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
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.
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.
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.
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.
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.
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
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
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
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
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
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
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
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