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