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 sector_size_str = get_bitbake_var('WIC_SECTOR_SIZE') 325 if sector_size_str is not None: 326 try: 327 self.sector_size = int(sector_size_str) 328 except ValueError: 329 self.sector_size = SECTOR_SIZE 330 else: 331 self.sector_size = SECTOR_SIZE 332 333 self.native_sysroot = native_sysroot 334 num_real_partitions = len([p for p in self.partitions if not p.no_table]) 335 self.extra_space = extra_space 336 337 # calculate the real partition number, accounting for partitions not 338 # in the partition table and logical partitions 339 realnum = 0 340 for part in self.partitions: 341 if part.no_table: 342 part.realnum = 0 343 else: 344 realnum += 1 345 if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4: 346 part.realnum = realnum + 1 347 continue 348 part.realnum = realnum 349 350 # generate parition and filesystem UUIDs 351 for part in self.partitions: 352 if not part.uuid and part.use_uuid: 353 if self.ptable_format in ('gpt', 'gpt-hybrid'): 354 part.uuid = str(uuid.uuid4()) 355 else: # msdos partition table 356 part.uuid = '%08x-%02d' % (self.identifier, part.realnum) 357 if not part.fsuuid: 358 if part.fstype == 'vfat' or part.fstype == 'msdos': 359 part.fsuuid = '0x' + str(uuid.uuid4())[:8].upper() 360 else: 361 part.fsuuid = str(uuid.uuid4()) 362 else: 363 #make sure the fsuuid for vfat/msdos align with format 0xYYYYYYYY 364 if part.fstype == 'vfat' or part.fstype == 'msdos': 365 if part.fsuuid.upper().startswith("0X"): 366 part.fsuuid = '0x' + part.fsuuid.upper()[2:].rjust(8,"0") 367 else: 368 part.fsuuid = '0x' + part.fsuuid.upper().rjust(8,"0") 369 370 def prepare(self, imager): 371 """Prepare an image. Call prepare method of all image partitions.""" 372 for part in self.partitions: 373 # need to create the filesystems in order to get their 374 # sizes before we can add them and do the layout. 375 part.prepare(imager, imager.workdir, imager.oe_builddir, 376 imager.rootfs_dir, imager.bootimg_dir, 377 imager.kernel_dir, imager.native_sysroot, 378 imager.updated_fstab_path) 379 380 # Converting kB to sectors for parted 381 part.size_sec = part.disk_size * 1024 // self.sector_size 382 383 def layout_partitions(self): 384 """ Layout the partitions, meaning calculate the position of every 385 partition on the disk. The 'ptable_format' parameter defines the 386 partition table format and may be "msdos". """ 387 388 logger.debug("Assigning %s partitions to disks", self.ptable_format) 389 390 # The number of primary and logical partitions. Extended partition and 391 # partitions not listed in the table are not included. 392 num_real_partitions = len([p for p in self.partitions if not p.no_table]) 393 394 # Go through partitions in the order they are added in .ks file 395 for num in range(len(self.partitions)): 396 part = self.partitions[num] 397 398 if self.ptable_format == 'msdos' and part.part_name: 399 raise WicError("setting custom partition name is not " \ 400 "implemented for msdos partitions") 401 402 if self.ptable_format == 'msdos' and part.part_type: 403 # The --part-type can also be implemented for MBR partitions, 404 # in which case it would map to the 1-byte "partition type" 405 # filed at offset 3 of the partition entry. 406 raise WicError("setting custom partition type is not " \ 407 "implemented for msdos partitions") 408 409 if part.mbr and self.ptable_format != 'gpt-hybrid': 410 raise WicError("Partition may only be included in MBR with " \ 411 "a gpt-hybrid partition table") 412 413 # Get the disk where the partition is located 414 self.numpart += 1 415 if not part.no_table: 416 self.realpart += 1 417 418 if self.numpart == 1: 419 if self.ptable_format == "msdos": 420 overhead = MBR_OVERHEAD 421 elif self.ptable_format in ("gpt", "gpt-hybrid"): 422 overhead = GPT_OVERHEAD 423 424 # Skip one sector required for the partitioning scheme overhead 425 self.offset += overhead 426 427 if self.ptable_format == "msdos": 428 if self.primary_part_num > 3 or \ 429 (self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4): 430 part.type = 'logical' 431 # Reserve a sector for EBR for every logical partition 432 # before alignment is performed. 433 if part.type == 'logical': 434 self.offset += 2 435 436 align_sectors = 0 437 if part.align: 438 # If not first partition and we do have alignment set we need 439 # to align the partition. 440 # FIXME: This leaves a empty spaces to the disk. To fill the 441 # gaps we could enlargea the previous partition? 442 443 # Calc how much the alignment is off. 444 align_sectors = self.offset % (part.align * 1024 // self.sector_size) 445 446 if align_sectors: 447 # If partition is not aligned as required, we need 448 # to move forward to the next alignment point 449 align_sectors = (part.align * 1024 // self.sector_size) - align_sectors 450 451 logger.debug("Realignment for %s%s with %s sectors, original" 452 " offset %s, target alignment is %sK.", 453 part.disk, self.numpart, align_sectors, 454 self.offset, part.align) 455 456 # increase the offset so we actually start the partition on right alignment 457 self.offset += align_sectors 458 459 if part.offset is not None: 460 offset = part.offset // self.sector_size 461 462 if offset * self.sector_size != part.offset: 463 raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size)) 464 465 delta = offset - self.offset 466 if delta < 0: 467 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)) 468 469 logger.debug("Skipping %d sectors to place %s%s at offset %dK", 470 delta, part.disk, self.numpart, part.offset) 471 472 self.offset = offset 473 474 part.start = self.offset 475 self.offset += part.size_sec 476 477 if not part.no_table: 478 part.num = self.realpart 479 else: 480 part.num = 0 481 482 if self.ptable_format == "msdos" and not part.no_table: 483 if part.type == 'logical': 484 self.logical_part_cnt += 1 485 part.num = self.logical_part_cnt + 4 486 if self.extendedpart == 0: 487 # Create extended partition as a primary partition 488 self.primary_part_num += 1 489 self.extendedpart = part.num 490 else: 491 self.extended_size_sec += align_sectors 492 self.extended_size_sec += part.size_sec + 2 493 else: 494 self.primary_part_num += 1 495 part.num = self.primary_part_num 496 497 logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " 498 "sectors (%d bytes).", part.mountpoint, part.disk, 499 part.num, part.start, self.offset - 1, part.size_sec, 500 part.size_sec * self.sector_size) 501 502 # Once all the partitions have been layed out, we can calculate the 503 # minumim disk size 504 self.min_size = self.offset 505 if self.ptable_format in ("gpt", "gpt-hybrid"): 506 self.min_size += GPT_OVERHEAD 507 508 self.min_size *= self.sector_size 509 self.min_size += self.extra_space 510 511 def _create_partition(self, device, parttype, fstype, start, size): 512 """ Create a partition on an image described by the 'device' object. """ 513 514 # Start is included to the size so we need to substract one from the end. 515 end = start + size - 1 516 logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors", 517 parttype, start, end, size) 518 519 cmd = "export PARTED_SECTOR_SIZE=%d; parted -s %s unit s mkpart %s" % \ 520 (self.sector_size, device, parttype) 521 if fstype: 522 cmd += " %s" % fstype 523 cmd += " %d %d" % (start, end) 524 525 return exec_native_cmd(cmd, self.native_sysroot) 526 527 def _write_identifier(self, device, identifier): 528 logger.debug("Set disk identifier %x", identifier) 529 with open(device, 'r+b') as img: 530 img.seek(0x1B8) 531 img.write(identifier.to_bytes(4, 'little')) 532 533 def _make_disk(self, device, ptable_format, min_size): 534 logger.debug("Creating sparse file %s", device) 535 with open(device, 'w') as sparse: 536 os.ftruncate(sparse.fileno(), min_size) 537 538 logger.debug("Initializing partition table for %s", device) 539 exec_native_cmd("export PARTED_SECTOR_SIZE=%d; parted -s %s mklabel %s" % 540 (self.sector_size, device, ptable_format), self.native_sysroot) 541 542 def _write_disk_guid(self): 543 if self.ptable_format in ('gpt', 'gpt-hybrid'): 544 if os.getenv('SOURCE_DATE_EPOCH'): 545 self.disk_guid = uuid.UUID(int=int(os.getenv('SOURCE_DATE_EPOCH'))) 546 else: 547 self.disk_guid = uuid.uuid4() 548 549 logger.debug("Set disk guid %s", self.disk_guid) 550 sfdisk_cmd = "sfdisk --sector-size %s --disk-id %s %s" % \ 551 (self.sector_size, self.path, self.disk_guid) 552 exec_native_cmd(sfdisk_cmd, self.native_sysroot) 553 554 def create(self): 555 self._make_disk(self.path, 556 "gpt" if self.ptable_format == "gpt-hybrid" else self.ptable_format, 557 self.min_size) 558 559 self._write_identifier(self.path, self.identifier) 560 self._write_disk_guid() 561 562 if self.ptable_format == "gpt-hybrid": 563 mbr_path = self.path + ".mbr" 564 self._make_disk(mbr_path, "msdos", self.min_size) 565 self._write_identifier(mbr_path, self.identifier) 566 567 logger.debug("Creating partitions") 568 569 hybrid_mbr_part_num = 0 570 571 for part in self.partitions: 572 if part.num == 0: 573 continue 574 575 if self.ptable_format == "msdos" and part.num == self.extendedpart: 576 # Create an extended partition (note: extended 577 # partition is described in MBR and contains all 578 # logical partitions). The logical partitions save a 579 # sector for an EBR just before the start of a 580 # partition. The extended partition must start one 581 # sector before the start of the first logical 582 # partition. This way the first EBR is inside of the 583 # extended partition. Since the extended partitions 584 # starts a sector before the first logical partition, 585 # add a sector at the back, so that there is enough 586 # room for all logical partitions. 587 self._create_partition(self.path, "extended", 588 None, part.start - 2, 589 self.extended_size_sec) 590 591 if part.fstype == "swap": 592 parted_fs_type = "linux-swap" 593 elif part.fstype == "vfat": 594 parted_fs_type = "fat32" 595 elif part.fstype == "msdos": 596 parted_fs_type = "fat16" 597 if not part.system_id: 598 part.system_id = '0x6' # FAT16 599 else: 600 # Type for ext2/ext3/ext4/btrfs 601 parted_fs_type = "ext2" 602 603 # Boot ROM of OMAP boards require vfat boot partition to have an 604 # even number of sectors. 605 if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \ 606 and part.size_sec % 2: 607 logger.debug("Subtracting one sector from '%s' partition to " 608 "get even number of sectors for the partition", 609 part.mountpoint) 610 part.size_sec -= 1 611 612 self._create_partition(self.path, part.type, 613 parted_fs_type, part.start, part.size_sec) 614 615 if self.ptable_format == "gpt-hybrid" and part.mbr: 616 hybrid_mbr_part_num += 1 617 if hybrid_mbr_part_num > 4: 618 raise WicError("Extended MBR partitions are not supported in hybrid MBR") 619 self._create_partition(mbr_path, "primary", 620 parted_fs_type, part.start, part.size_sec) 621 622 if self.ptable_format in ("gpt", "gpt-hybrid") and (part.part_name or part.label): 623 partition_label = part.part_name if part.part_name else part.label 624 logger.debug("partition %d: set name to %s", 625 part.num, partition_label) 626 exec_native_cmd("sfdisk --sector-size %s --part-label %s %d %s" % \ 627 (self.sector_size, self.path, part.num, 628 partition_label), self.native_sysroot) 629 if part.part_type: 630 logger.debug("partition %d: set type UID to %s", 631 part.num, part.part_type) 632 exec_native_cmd("sfdisk --sector-size %s --part-type %s %d %s" % \ 633 (self.sector_size, self.path, part.num, 634 part.part_type), self.native_sysroot) 635 636 if part.uuid and self.ptable_format in ("gpt", "gpt-hybrid"): 637 logger.debug("partition %d: set UUID to %s", 638 part.num, part.uuid) 639 exec_native_cmd("sfdisk --sector-size %s --part-uuid %s %d %s" % \ 640 (self.sector_size, self.path, part.num, part.uuid), 641 self.native_sysroot) 642 643 if part.active: 644 flag_name = "legacy_boot" if self.ptable_format in ('gpt', 'gpt-hybrid') else "boot" 645 logger.debug("Set '%s' flag for partition '%s' on disk '%s'", 646 flag_name, part.num, self.path) 647 exec_native_cmd("export PARTED_SECTOR_SIZE=%d; parted -s %s set %d %s on" % \ 648 (self.sector_size, self.path, part.num, flag_name), 649 self.native_sysroot) 650 if self.ptable_format == 'gpt-hybrid' and part.mbr: 651 exec_native_cmd("export PARTED_SECTOR_SIZE=%d; parted -s %s set %d %s on" % \ 652 (self.sector_size, mbr_path, hybrid_mbr_part_num, "boot"), 653 self.native_sysroot) 654 if part.system_id: 655 exec_native_cmd("sfdisk --sector-size %s --part-type %s %s %s" % \ 656 (self.sector_size, self.path, part.num, part.system_id), 657 self.native_sysroot) 658 659 if part.hidden and self.ptable_format == "gpt": 660 logger.debug("Set hidden attribute for partition '%s' on disk '%s'", 661 part.num, self.path) 662 exec_native_cmd("sfdisk --sector-size %s --part-attrs %s %s RequiredPartition" % \ 663 (self.sector_size, self.path, part.num), 664 self.native_sysroot) 665 666 if self.ptable_format == "gpt-hybrid": 667 # Write a protective GPT partition 668 hybrid_mbr_part_num += 1 669 if hybrid_mbr_part_num > 4: 670 raise WicError("Extended MBR partitions are not supported in hybrid MBR") 671 672 # parted cannot directly create a protective GPT partition, so 673 # create with an arbitrary type, then change it to the correct type 674 # with sfdisk 675 self._create_partition(mbr_path, "primary", "fat32", 1, GPT_OVERHEAD) 676 exec_native_cmd("sfdisk --sector-size %s --part-type %s %d 0xee" % \ 677 (self.sector_size, mbr_path, hybrid_mbr_part_num), 678 self.native_sysroot) 679 680 # Copy hybrid MBR 681 with open(mbr_path, "rb") as mbr_file: 682 with open(self.path, "r+b") as image_file: 683 mbr = mbr_file.read(512) 684 image_file.write(mbr) 685 686 def cleanup(self): 687 pass 688 689 def assemble(self): 690 logger.debug("Installing partitions") 691 692 for part in self.partitions: 693 source = part.source_file 694 if source: 695 # install source_file contents into a partition 696 sparse_copy(source, self.path, seek=part.start * self.sector_size) 697 698 logger.debug("Installed %s in partition %d, sectors %d-%d, " 699 "size %d sectors", source, part.num, part.start, 700 part.start + part.size_sec - 1, part.size_sec) 701 702 partimage = self.path + '.p%d' % part.num 703 os.rename(source, partimage) 704 self.partimages.append(partimage) 705