1# 2# Copyright (c) 2013, Intel Corporation. 3# 4# SPDX-License-Identifier: GPL-2.0-only 5# 6# DESCRIPTION 7# This implements the 'direct' imager plugin class for 'wic' 8# 9# AUTHORS 10# Tom Zanussi <tom.zanussi (at] linux.intel.com> 11# 12 13import logging 14import os 15import random 16import shutil 17import tempfile 18import uuid 19 20from time import strftime 21 22from oe.path import copyhardlinktree 23 24from wic import WicError 25from wic.filemap import sparse_copy 26from wic.ksparser import KickStart, KickStartError 27from wic.pluginbase import PluginMgr, ImagerPlugin 28from wic.misc import get_bitbake_var, exec_cmd, exec_native_cmd 29 30logger = logging.getLogger('wic') 31 32class DirectPlugin(ImagerPlugin): 33 """ 34 Install a system into a file containing a partitioned disk image. 35 36 An image file is formatted with a partition table, each partition 37 created from a rootfs or other OpenEmbedded build artifact and dd'ed 38 into the virtual disk. The disk image can subsequently be dd'ed onto 39 media and used on actual hardware. 40 """ 41 name = 'direct' 42 43 def __init__(self, wks_file, rootfs_dir, bootimg_dir, kernel_dir, 44 native_sysroot, oe_builddir, options): 45 try: 46 self.ks = KickStart(wks_file) 47 except KickStartError as err: 48 raise WicError(str(err)) 49 50 # parse possible 'rootfs=name' items 51 self.rootfs_dir = dict(rdir.split('=') for rdir in rootfs_dir.split(' ')) 52 self.bootimg_dir = bootimg_dir 53 self.kernel_dir = kernel_dir 54 self.native_sysroot = native_sysroot 55 self.oe_builddir = oe_builddir 56 57 self.debug = options.debug 58 self.outdir = options.outdir 59 self.compressor = options.compressor 60 self.bmap = options.bmap 61 self.no_fstab_update = options.no_fstab_update 62 self.updated_fstab_path = None 63 64 self.name = "%s-%s" % (os.path.splitext(os.path.basename(wks_file))[0], 65 strftime("%Y%m%d%H%M")) 66 self.workdir = self.setup_workdir(options.workdir) 67 self._image = None 68 self.ptable_format = self.ks.bootloader.ptable 69 self.parts = self.ks.partitions 70 71 # as a convenience, set source to the boot partition source 72 # instead of forcing it to be set via bootloader --source 73 for part in self.parts: 74 if not self.ks.bootloader.source and part.mountpoint == "/boot": 75 self.ks.bootloader.source = part.source 76 break 77 78 image_path = self._full_path(self.workdir, self.parts[0].disk, "direct") 79 self._image = PartitionedImage(image_path, self.ptable_format, 80 self.parts, self.native_sysroot, 81 options.extra_space) 82 83 def setup_workdir(self, workdir): 84 if workdir: 85 if os.path.exists(workdir): 86 raise WicError("Internal workdir '%s' specified in wic arguments already exists!" % (workdir)) 87 88 os.makedirs(workdir) 89 return workdir 90 else: 91 return tempfile.mkdtemp(dir=self.outdir, prefix='tmp.wic.') 92 93 def do_create(self): 94 """ 95 Plugin entry point. 96 """ 97 try: 98 self.create() 99 self.assemble() 100 self.finalize() 101 self.print_info() 102 finally: 103 self.cleanup() 104 105 def update_fstab(self, image_rootfs): 106 """Assume partition order same as in wks""" 107 if not image_rootfs: 108 return 109 110 fstab_path = image_rootfs + "/etc/fstab" 111 if not os.path.isfile(fstab_path): 112 return 113 114 with open(fstab_path) as fstab: 115 fstab_lines = fstab.readlines() 116 117 updated = False 118 for part in self.parts: 119 if not part.realnum or not part.mountpoint \ 120 or part.mountpoint == "/" or not (part.mountpoint.startswith('/') or part.mountpoint == "swap"): 121 continue 122 123 if part.use_uuid: 124 if part.fsuuid: 125 # FAT UUID is different from others 126 if len(part.fsuuid) == 10: 127 device_name = "UUID=%s-%s" % \ 128 (part.fsuuid[2:6], part.fsuuid[6:]) 129 else: 130 device_name = "UUID=%s" % part.fsuuid 131 else: 132 device_name = "PARTUUID=%s" % part.uuid 133 elif part.use_label: 134 device_name = "LABEL=%s" % part.label 135 else: 136 # mmc device partitions are named mmcblk0p1, mmcblk0p2.. 137 prefix = 'p' if part.disk.startswith('mmcblk') else '' 138 device_name = "/dev/%s%s%d" % (part.disk, prefix, part.realnum) 139 140 opts = part.fsopts if part.fsopts else "defaults" 141 passno = part.fspassno if part.fspassno else "0" 142 line = "\t".join([device_name, part.mountpoint, part.fstype, 143 opts, "0", passno]) + "\n" 144 145 fstab_lines.append(line) 146 updated = True 147 148 if updated: 149 self.updated_fstab_path = os.path.join(self.workdir, "fstab") 150 with open(self.updated_fstab_path, "w") as f: 151 f.writelines(fstab_lines) 152 if os.getenv('SOURCE_DATE_EPOCH'): 153 fstab_time = int(os.getenv('SOURCE_DATE_EPOCH')) 154 os.utime(self.updated_fstab_path, (fstab_time, fstab_time)) 155 156 def _full_path(self, path, name, extention): 157 """ Construct full file path to a file we generate. """ 158 return os.path.join(path, "%s-%s.%s" % (self.name, name, extention)) 159 160 # 161 # Actual implemention 162 # 163 def create(self): 164 """ 165 For 'wic', we already have our build artifacts - we just create 166 filesystems from the artifacts directly and combine them into 167 a partitioned image. 168 """ 169 if not self.no_fstab_update: 170 self.update_fstab(self.rootfs_dir.get("ROOTFS_DIR")) 171 172 for part in self.parts: 173 # get rootfs size from bitbake variable if it's not set in .ks file 174 if not part.size: 175 # and if rootfs name is specified for the partition 176 image_name = self.rootfs_dir.get(part.rootfs_dir) 177 if image_name and os.path.sep not in image_name: 178 # Bitbake variable ROOTFS_SIZE is calculated in 179 # Image._get_rootfs_size method from meta/lib/oe/image.py 180 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT, 181 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE 182 rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name) 183 if rsize_bb: 184 part.size = int(round(float(rsize_bb))) 185 186 self._image.prepare(self) 187 self._image.layout_partitions() 188 self._image.create() 189 190 def assemble(self): 191 """ 192 Assemble partitions into disk image 193 """ 194 self._image.assemble() 195 196 def finalize(self): 197 """ 198 Finalize the disk image. 199 200 For example, prepare the image to be bootable by e.g. 201 creating and installing a bootloader configuration. 202 """ 203 source_plugin = self.ks.bootloader.source 204 disk_name = self.parts[0].disk 205 if source_plugin: 206 plugin = PluginMgr.get_plugins('source')[source_plugin] 207 plugin.do_install_disk(self._image, disk_name, self, self.workdir, 208 self.oe_builddir, self.bootimg_dir, 209 self.kernel_dir, self.native_sysroot) 210 211 full_path = self._image.path 212 # Generate .bmap 213 if self.bmap: 214 logger.debug("Generating bmap file for %s", disk_name) 215 python = os.path.join(self.native_sysroot, 'usr/bin/python3-native/python3') 216 bmaptool = os.path.join(self.native_sysroot, 'usr/bin/bmaptool') 217 exec_native_cmd("%s %s create %s -o %s.bmap" % \ 218 (python, bmaptool, full_path, full_path), self.native_sysroot) 219 # Compress the image 220 if self.compressor: 221 logger.debug("Compressing disk %s with %s", disk_name, self.compressor) 222 exec_cmd("%s %s" % (self.compressor, full_path)) 223 224 def print_info(self): 225 """ 226 Print the image(s) and artifacts used, for the user. 227 """ 228 msg = "The new image(s) can be found here:\n" 229 230 extension = "direct" + {"gzip": ".gz", 231 "bzip2": ".bz2", 232 "xz": ".xz", 233 None: ""}.get(self.compressor) 234 full_path = self._full_path(self.outdir, self.parts[0].disk, extension) 235 msg += ' %s\n\n' % full_path 236 237 msg += 'The following build artifacts were used to create the image(s):\n' 238 for part in self.parts: 239 if part.rootfs_dir is None: 240 continue 241 if part.mountpoint == '/': 242 suffix = ':' 243 else: 244 suffix = '["%s"]:' % (part.mountpoint or part.label) 245 rootdir = part.rootfs_dir 246 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), rootdir) 247 248 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir 249 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir 250 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot 251 252 logger.info(msg) 253 254 @property 255 def rootdev(self): 256 """ 257 Get root device name to use as a 'root' parameter 258 in kernel command line. 259 260 Assume partition order same as in wks 261 """ 262 for part in self.parts: 263 if part.mountpoint == "/": 264 if part.uuid: 265 return "PARTUUID=%s" % part.uuid 266 elif part.label and self.ptable_format != 'msdos': 267 return "PARTLABEL=%s" % part.label 268 else: 269 suffix = 'p' if part.disk.startswith('mmcblk') else '' 270 return "/dev/%s%s%-d" % (part.disk, suffix, part.realnum) 271 272 def cleanup(self): 273 if self._image: 274 self._image.cleanup() 275 276 # Move results to the output dir 277 if not os.path.exists(self.outdir): 278 os.makedirs(self.outdir) 279 280 for fname in os.listdir(self.workdir): 281 path = os.path.join(self.workdir, fname) 282 if os.path.isfile(path): 283 shutil.move(path, os.path.join(self.outdir, fname)) 284 285 # remove work directory when it is not in debugging mode 286 if not self.debug: 287 shutil.rmtree(self.workdir, ignore_errors=True) 288 289# Overhead of the MBR partitioning scheme (just one sector) 290MBR_OVERHEAD = 1 291 292# Overhead of the GPT partitioning scheme 293GPT_OVERHEAD = 34 294 295# Size of a sector in bytes 296SECTOR_SIZE = 512 297 298class PartitionedImage(): 299 """ 300 Partitioned image in a file. 301 """ 302 303 def __init__(self, path, ptable_format, partitions, native_sysroot=None, extra_space=0): 304 self.path = path # Path to the image file 305 self.numpart = 0 # Number of allocated partitions 306 self.realpart = 0 # Number of partitions in the partition table 307 self.primary_part_num = 0 # Number of primary partitions (msdos) 308 self.extendedpart = 0 # Create extended partition before this logical partition (msdos) 309 self.extended_size_sec = 0 # Size of exteded partition (msdos) 310 self.logical_part_cnt = 0 # Number of total logical paritions (msdos) 311 self.offset = 0 # Offset of next partition (in sectors) 312 self.min_size = 0 # Minimum required disk size to fit 313 # all partitions (in bytes) 314 self.ptable_format = ptable_format # Partition table format 315 # Disk system identifier 316 if os.getenv('SOURCE_DATE_EPOCH'): 317 self.identifier = random.Random(int(os.getenv('SOURCE_DATE_EPOCH'))).randint(1, 0xffffffff) 318 else: 319 self.identifier = random.SystemRandom().randint(1, 0xffffffff) 320 321 self.partitions = partitions 322 self.partimages = [] 323 # Size of a sector used in calculations 324 self.sector_size = SECTOR_SIZE 325 self.native_sysroot = native_sysroot 326 num_real_partitions = len([p for p in self.partitions if not p.no_table]) 327 self.extra_space = extra_space 328 329 # calculate the real partition number, accounting for partitions not 330 # in the partition table and logical partitions 331 realnum = 0 332 for part in self.partitions: 333 if part.no_table: 334 part.realnum = 0 335 else: 336 realnum += 1 337 if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4: 338 part.realnum = realnum + 1 339 continue 340 part.realnum = realnum 341 342 # generate parition and filesystem UUIDs 343 for part in self.partitions: 344 if not part.uuid and part.use_uuid: 345 if self.ptable_format in ('gpt', 'gpt-hybrid'): 346 part.uuid = str(uuid.uuid4()) 347 else: # msdos partition table 348 part.uuid = '%08x-%02d' % (self.identifier, part.realnum) 349 if not part.fsuuid: 350 if part.fstype == 'vfat' or part.fstype == 'msdos': 351 part.fsuuid = '0x' + str(uuid.uuid4())[:8].upper() 352 else: 353 part.fsuuid = str(uuid.uuid4()) 354 else: 355 #make sure the fsuuid for vfat/msdos align with format 0xYYYYYYYY 356 if part.fstype == 'vfat' or part.fstype == 'msdos': 357 if part.fsuuid.upper().startswith("0X"): 358 part.fsuuid = '0x' + part.fsuuid.upper()[2:].rjust(8,"0") 359 else: 360 part.fsuuid = '0x' + part.fsuuid.upper().rjust(8,"0") 361 362 def prepare(self, imager): 363 """Prepare an image. Call prepare method of all image partitions.""" 364 for part in self.partitions: 365 # need to create the filesystems in order to get their 366 # sizes before we can add them and do the layout. 367 part.prepare(imager, imager.workdir, imager.oe_builddir, 368 imager.rootfs_dir, imager.bootimg_dir, 369 imager.kernel_dir, imager.native_sysroot, 370 imager.updated_fstab_path) 371 372 # Converting kB to sectors for parted 373 part.size_sec = part.disk_size * 1024 // self.sector_size 374 375 def layout_partitions(self): 376 """ Layout the partitions, meaning calculate the position of every 377 partition on the disk. The 'ptable_format' parameter defines the 378 partition table format and may be "msdos". """ 379 380 logger.debug("Assigning %s partitions to disks", self.ptable_format) 381 382 # The number of primary and logical partitions. Extended partition and 383 # partitions not listed in the table are not included. 384 num_real_partitions = len([p for p in self.partitions if not p.no_table]) 385 386 # Go through partitions in the order they are added in .ks file 387 for num in range(len(self.partitions)): 388 part = self.partitions[num] 389 390 if self.ptable_format == 'msdos' and part.part_name: 391 raise WicError("setting custom partition name is not " \ 392 "implemented for msdos partitions") 393 394 if self.ptable_format == 'msdos' and part.part_type: 395 # The --part-type can also be implemented for MBR partitions, 396 # in which case it would map to the 1-byte "partition type" 397 # filed at offset 3 of the partition entry. 398 raise WicError("setting custom partition type is not " \ 399 "implemented for msdos partitions") 400 401 if part.mbr and self.ptable_format != 'gpt-hybrid': 402 raise WicError("Partition may only be included in MBR with " \ 403 "a gpt-hybrid partition table") 404 405 # Get the disk where the partition is located 406 self.numpart += 1 407 if not part.no_table: 408 self.realpart += 1 409 410 if self.numpart == 1: 411 if self.ptable_format == "msdos": 412 overhead = MBR_OVERHEAD 413 elif self.ptable_format in ("gpt", "gpt-hybrid"): 414 overhead = GPT_OVERHEAD 415 416 # Skip one sector required for the partitioning scheme overhead 417 self.offset += overhead 418 419 if self.ptable_format == "msdos": 420 if self.primary_part_num > 3 or \ 421 (self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4): 422 part.type = 'logical' 423 # Reserve a sector for EBR for every logical partition 424 # before alignment is performed. 425 if part.type == 'logical': 426 self.offset += 2 427 428 align_sectors = 0 429 if part.align: 430 # If not first partition and we do have alignment set we need 431 # to align the partition. 432 # FIXME: This leaves a empty spaces to the disk. To fill the 433 # gaps we could enlargea the previous partition? 434 435 # Calc how much the alignment is off. 436 align_sectors = self.offset % (part.align * 1024 // self.sector_size) 437 438 if align_sectors: 439 # If partition is not aligned as required, we need 440 # to move forward to the next alignment point 441 align_sectors = (part.align * 1024 // self.sector_size) - align_sectors 442 443 logger.debug("Realignment for %s%s with %s sectors, original" 444 " offset %s, target alignment is %sK.", 445 part.disk, self.numpart, align_sectors, 446 self.offset, part.align) 447 448 # increase the offset so we actually start the partition on right alignment 449 self.offset += align_sectors 450 451 if part.offset is not None: 452 offset = part.offset // self.sector_size 453 454 if offset * self.sector_size != part.offset: 455 raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size)) 456 457 delta = offset - self.offset 458 if delta < 0: 459 raise WicError("Could not place %s%s at offset %d: next free sector is %d (delta: %d)" % (part.disk, self.numpart, part.offset, self.offset, delta)) 460 461 logger.debug("Skipping %d sectors to place %s%s at offset %dK", 462 delta, part.disk, self.numpart, part.offset) 463 464 self.offset = offset 465 466 part.start = self.offset 467 self.offset += part.size_sec 468 469 if not part.no_table: 470 part.num = self.realpart 471 else: 472 part.num = 0 473 474 if self.ptable_format == "msdos" and not part.no_table: 475 if part.type == 'logical': 476 self.logical_part_cnt += 1 477 part.num = self.logical_part_cnt + 4 478 if self.extendedpart == 0: 479 # Create extended partition as a primary partition 480 self.primary_part_num += 1 481 self.extendedpart = part.num 482 else: 483 self.extended_size_sec += align_sectors 484 self.extended_size_sec += part.size_sec + 2 485 else: 486 self.primary_part_num += 1 487 part.num = self.primary_part_num 488 489 logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " 490 "sectors (%d bytes).", part.mountpoint, part.disk, 491 part.num, part.start, self.offset - 1, part.size_sec, 492 part.size_sec * self.sector_size) 493 494 # Once all the partitions have been layed out, we can calculate the 495 # minumim disk size 496 self.min_size = self.offset 497 if self.ptable_format in ("gpt", "gpt-hybrid"): 498 self.min_size += GPT_OVERHEAD 499 500 self.min_size *= self.sector_size 501 self.min_size += self.extra_space 502 503 def _create_partition(self, device, parttype, fstype, start, size): 504 """ Create a partition on an image described by the 'device' object. """ 505 506 # Start is included to the size so we need to substract one from the end. 507 end = start + size - 1 508 logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors", 509 parttype, start, end, size) 510 511 cmd = "parted -s %s unit s mkpart %s" % (device, parttype) 512 if fstype: 513 cmd += " %s" % fstype 514 cmd += " %d %d" % (start, end) 515 516 return exec_native_cmd(cmd, self.native_sysroot) 517 518 def _write_identifier(self, device, identifier): 519 logger.debug("Set disk identifier %x", identifier) 520 with open(device, 'r+b') as img: 521 img.seek(0x1B8) 522 img.write(identifier.to_bytes(4, 'little')) 523 524 def _make_disk(self, device, ptable_format, min_size): 525 logger.debug("Creating sparse file %s", device) 526 with open(device, 'w') as sparse: 527 os.ftruncate(sparse.fileno(), min_size) 528 529 logger.debug("Initializing partition table for %s", device) 530 exec_native_cmd("parted -s %s mklabel %s" % (device, ptable_format), 531 self.native_sysroot) 532 533 def _write_disk_guid(self): 534 if self.ptable_format in ('gpt', 'gpt-hybrid'): 535 if os.getenv('SOURCE_DATE_EPOCH'): 536 self.disk_guid = uuid.UUID(int=int(os.getenv('SOURCE_DATE_EPOCH'))) 537 else: 538 self.disk_guid = uuid.uuid4() 539 540 logger.debug("Set disk guid %s", self.disk_guid) 541 sfdisk_cmd = "sfdisk --disk-id %s %s" % (self.path, self.disk_guid) 542 exec_native_cmd(sfdisk_cmd, self.native_sysroot) 543 544 def create(self): 545 self._make_disk(self.path, 546 "gpt" if self.ptable_format == "gpt-hybrid" else self.ptable_format, 547 self.min_size) 548 549 self._write_identifier(self.path, self.identifier) 550 self._write_disk_guid() 551 552 if self.ptable_format == "gpt-hybrid": 553 mbr_path = self.path + ".mbr" 554 self._make_disk(mbr_path, "msdos", self.min_size) 555 self._write_identifier(mbr_path, self.identifier) 556 557 logger.debug("Creating partitions") 558 559 hybrid_mbr_part_num = 0 560 561 for part in self.partitions: 562 if part.num == 0: 563 continue 564 565 if self.ptable_format == "msdos" and part.num == self.extendedpart: 566 # Create an extended partition (note: extended 567 # partition is described in MBR and contains all 568 # logical partitions). The logical partitions save a 569 # sector for an EBR just before the start of a 570 # partition. The extended partition must start one 571 # sector before the start of the first logical 572 # partition. This way the first EBR is inside of the 573 # extended partition. Since the extended partitions 574 # starts a sector before the first logical partition, 575 # add a sector at the back, so that there is enough 576 # room for all logical partitions. 577 self._create_partition(self.path, "extended", 578 None, part.start - 2, 579 self.extended_size_sec) 580 581 if part.fstype == "swap": 582 parted_fs_type = "linux-swap" 583 elif part.fstype == "vfat": 584 parted_fs_type = "fat32" 585 elif part.fstype == "msdos": 586 parted_fs_type = "fat16" 587 if not part.system_id: 588 part.system_id = '0x6' # FAT16 589 else: 590 # Type for ext2/ext3/ext4/btrfs 591 parted_fs_type = "ext2" 592 593 # Boot ROM of OMAP boards require vfat boot partition to have an 594 # even number of sectors. 595 if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \ 596 and part.size_sec % 2: 597 logger.debug("Subtracting one sector from '%s' partition to " 598 "get even number of sectors for the partition", 599 part.mountpoint) 600 part.size_sec -= 1 601 602 self._create_partition(self.path, part.type, 603 parted_fs_type, part.start, part.size_sec) 604 605 if self.ptable_format == "gpt-hybrid" and part.mbr: 606 hybrid_mbr_part_num += 1 607 if hybrid_mbr_part_num > 4: 608 raise WicError("Extended MBR partitions are not supported in hybrid MBR") 609 self._create_partition(mbr_path, "primary", 610 parted_fs_type, part.start, part.size_sec) 611 612 if self.ptable_format in ("gpt", "gpt-hybrid") and (part.part_name or part.label): 613 partition_label = part.part_name if part.part_name else part.label 614 logger.debug("partition %d: set name to %s", 615 part.num, partition_label) 616 exec_native_cmd("sgdisk --change-name=%d:%s %s" % \ 617 (part.num, partition_label, 618 self.path), self.native_sysroot) 619 620 if part.part_type: 621 logger.debug("partition %d: set type UID to %s", 622 part.num, part.part_type) 623 exec_native_cmd("sgdisk --typecode=%d:%s %s" % \ 624 (part.num, part.part_type, 625 self.path), self.native_sysroot) 626 627 if part.uuid and self.ptable_format in ("gpt", "gpt-hybrid"): 628 logger.debug("partition %d: set UUID to %s", 629 part.num, part.uuid) 630 exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \ 631 (part.num, part.uuid, self.path), 632 self.native_sysroot) 633 634 if part.active: 635 flag_name = "legacy_boot" if self.ptable_format in ('gpt', 'gpt-hybrid') else "boot" 636 logger.debug("Set '%s' flag for partition '%s' on disk '%s'", 637 flag_name, part.num, self.path) 638 exec_native_cmd("parted -s %s set %d %s on" % \ 639 (self.path, part.num, flag_name), 640 self.native_sysroot) 641 if self.ptable_format == 'gpt-hybrid' and part.mbr: 642 exec_native_cmd("parted -s %s set %d %s on" % \ 643 (mbr_path, hybrid_mbr_part_num, "boot"), 644 self.native_sysroot) 645 if part.system_id: 646 exec_native_cmd("sfdisk --part-type %s %s %s" % \ 647 (self.path, part.num, part.system_id), 648 self.native_sysroot) 649 650 if part.hidden and self.ptable_format == "gpt": 651 logger.debug("Set hidden attribute for partition '%s' on disk '%s'", 652 part.num, self.path) 653 exec_native_cmd("sfdisk --part-attrs %s %s RequiredPartition" % \ 654 (self.path, part.num), 655 self.native_sysroot) 656 657 if self.ptable_format == "gpt-hybrid": 658 # Write a protective GPT partition 659 hybrid_mbr_part_num += 1 660 if hybrid_mbr_part_num > 4: 661 raise WicError("Extended MBR partitions are not supported in hybrid MBR") 662 663 # parted cannot directly create a protective GPT partition, so 664 # create with an arbitrary type, then change it to the correct type 665 # with sfdisk 666 self._create_partition(mbr_path, "primary", "fat32", 1, GPT_OVERHEAD) 667 exec_native_cmd("sfdisk --part-type %s %d 0xee" % (mbr_path, hybrid_mbr_part_num), 668 self.native_sysroot) 669 670 # Copy hybrid MBR 671 with open(mbr_path, "rb") as mbr_file: 672 with open(self.path, "r+b") as image_file: 673 mbr = mbr_file.read(512) 674 image_file.write(mbr) 675 676 def cleanup(self): 677 pass 678 679 def assemble(self): 680 logger.debug("Installing partitions") 681 682 for part in self.partitions: 683 source = part.source_file 684 if source: 685 # install source_file contents into a partition 686 sparse_copy(source, self.path, seek=part.start * self.sector_size) 687 688 logger.debug("Installed %s in partition %d, sectors %d-%d, " 689 "size %d sectors", source, part.num, part.start, 690 part.start + part.size_sec - 1, part.size_sec) 691 692 partimage = self.path + '.p%d' % part.num 693 os.rename(source, partimage) 694 self.partimages.append(partimage) 695