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