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 57*d1e89497SAndrew 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 62*d1e89497SAndrew 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")) 66*d1e89497SAndrew 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, 80eb8dc403SDave Cobbley self.parts, self.native_sysroot) 81eb8dc403SDave Cobbley 82*d1e89497SAndrew Geissler def setup_workdir(self, workdir): 83*d1e89497SAndrew Geissler if workdir: 84*d1e89497SAndrew Geissler if os.path.exists(workdir): 85*d1e89497SAndrew Geissler raise WicError("Internal workdir '%s' specified in wic arguments already exists!" % (workdir)) 86*d1e89497SAndrew Geissler 87*d1e89497SAndrew Geissler os.makedirs(workdir) 88*d1e89497SAndrew Geissler return workdir 89*d1e89497SAndrew Geissler else: 90*d1e89497SAndrew Geissler return tempfile.mkdtemp(dir=self.outdir, prefix='tmp.wic.') 91*d1e89497SAndrew Geissler 92eb8dc403SDave Cobbley def do_create(self): 93eb8dc403SDave Cobbley """ 94eb8dc403SDave Cobbley Plugin entry point. 95eb8dc403SDave Cobbley """ 96eb8dc403SDave Cobbley try: 97eb8dc403SDave Cobbley self.create() 98eb8dc403SDave Cobbley self.assemble() 99eb8dc403SDave Cobbley self.finalize() 100eb8dc403SDave Cobbley self.print_info() 101eb8dc403SDave Cobbley finally: 102eb8dc403SDave Cobbley self.cleanup() 103eb8dc403SDave Cobbley 104*d1e89497SAndrew Geissler def update_fstab(self, image_rootfs): 105*d1e89497SAndrew Geissler """Assume partition order same as in wks""" 106eb8dc403SDave Cobbley if not image_rootfs: 107eb8dc403SDave Cobbley return 108eb8dc403SDave Cobbley 109eb8dc403SDave Cobbley fstab_path = image_rootfs + "/etc/fstab" 110eb8dc403SDave Cobbley if not os.path.isfile(fstab_path): 111eb8dc403SDave Cobbley return 112eb8dc403SDave Cobbley 113eb8dc403SDave Cobbley with open(fstab_path) as fstab: 114eb8dc403SDave Cobbley fstab_lines = fstab.readlines() 115eb8dc403SDave Cobbley 116eb8dc403SDave Cobbley updated = False 117*d1e89497SAndrew Geissler for part in self.parts: 118eb8dc403SDave Cobbley if not part.realnum or not part.mountpoint \ 119eb8dc403SDave Cobbley or part.mountpoint == "/": 120eb8dc403SDave Cobbley continue 121eb8dc403SDave Cobbley 122eb8dc403SDave Cobbley if part.use_uuid: 123eb8dc403SDave Cobbley if part.fsuuid: 124eb8dc403SDave Cobbley # FAT UUID is different from others 125eb8dc403SDave Cobbley if len(part.fsuuid) == 10: 126eb8dc403SDave Cobbley device_name = "UUID=%s-%s" % \ 127eb8dc403SDave Cobbley (part.fsuuid[2:6], part.fsuuid[6:]) 128eb8dc403SDave Cobbley else: 129eb8dc403SDave Cobbley device_name = "UUID=%s" % part.fsuuid 130eb8dc403SDave Cobbley else: 131eb8dc403SDave Cobbley device_name = "PARTUUID=%s" % part.uuid 1321a4b7ee2SBrad Bishop elif part.use_label: 1331a4b7ee2SBrad Bishop device_name = "LABEL=%s" % part.label 134eb8dc403SDave Cobbley else: 135eb8dc403SDave Cobbley # mmc device partitions are named mmcblk0p1, mmcblk0p2.. 136eb8dc403SDave Cobbley prefix = 'p' if part.disk.startswith('mmcblk') else '' 137eb8dc403SDave Cobbley device_name = "/dev/%s%s%d" % (part.disk, prefix, part.realnum) 138eb8dc403SDave Cobbley 139eb8dc403SDave Cobbley opts = part.fsopts if part.fsopts else "defaults" 140eb8dc403SDave Cobbley line = "\t".join([device_name, part.mountpoint, part.fstype, 141eb8dc403SDave Cobbley opts, "0", "0"]) + "\n" 142eb8dc403SDave Cobbley 143eb8dc403SDave Cobbley fstab_lines.append(line) 144eb8dc403SDave Cobbley updated = True 145eb8dc403SDave Cobbley 146*d1e89497SAndrew Geissler if updated: 147*d1e89497SAndrew Geissler self.updated_fstab_path = os.path.join(self.workdir, "fstab") 148*d1e89497SAndrew Geissler with open(self.updated_fstab_path, "w") as f: 149*d1e89497SAndrew Geissler f.writelines(fstab_lines) 150eb8dc403SDave Cobbley 151eb8dc403SDave Cobbley def _full_path(self, path, name, extention): 152eb8dc403SDave Cobbley """ Construct full file path to a file we generate. """ 153eb8dc403SDave Cobbley return os.path.join(path, "%s-%s.%s" % (self.name, name, extention)) 154eb8dc403SDave Cobbley 155eb8dc403SDave Cobbley # 156eb8dc403SDave Cobbley # Actual implemention 157eb8dc403SDave Cobbley # 158eb8dc403SDave Cobbley def create(self): 159eb8dc403SDave Cobbley """ 160eb8dc403SDave Cobbley For 'wic', we already have our build artifacts - we just create 161eb8dc403SDave Cobbley filesystems from the artifacts directly and combine them into 162eb8dc403SDave Cobbley a partitioned image. 163eb8dc403SDave Cobbley """ 16496ff1984SBrad Bishop if not self.no_fstab_update: 165*d1e89497SAndrew Geissler self.update_fstab(self.rootfs_dir.get("ROOTFS_DIR")) 166eb8dc403SDave Cobbley 167eb8dc403SDave Cobbley for part in self.parts: 168eb8dc403SDave Cobbley # get rootfs size from bitbake variable if it's not set in .ks file 169eb8dc403SDave Cobbley if not part.size: 170eb8dc403SDave Cobbley # and if rootfs name is specified for the partition 171eb8dc403SDave Cobbley image_name = self.rootfs_dir.get(part.rootfs_dir) 172eb8dc403SDave Cobbley if image_name and os.path.sep not in image_name: 173eb8dc403SDave Cobbley # Bitbake variable ROOTFS_SIZE is calculated in 174eb8dc403SDave Cobbley # Image._get_rootfs_size method from meta/lib/oe/image.py 175eb8dc403SDave Cobbley # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT, 176eb8dc403SDave Cobbley # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE 177eb8dc403SDave Cobbley rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name) 178eb8dc403SDave Cobbley if rsize_bb: 179eb8dc403SDave Cobbley part.size = int(round(float(rsize_bb))) 180eb8dc403SDave Cobbley 181eb8dc403SDave Cobbley self._image.prepare(self) 182eb8dc403SDave Cobbley self._image.layout_partitions() 183eb8dc403SDave Cobbley self._image.create() 184eb8dc403SDave Cobbley 185eb8dc403SDave Cobbley def assemble(self): 186eb8dc403SDave Cobbley """ 187eb8dc403SDave Cobbley Assemble partitions into disk image 188eb8dc403SDave Cobbley """ 189eb8dc403SDave Cobbley self._image.assemble() 190eb8dc403SDave Cobbley 191eb8dc403SDave Cobbley def finalize(self): 192eb8dc403SDave Cobbley """ 193eb8dc403SDave Cobbley Finalize the disk image. 194eb8dc403SDave Cobbley 195eb8dc403SDave Cobbley For example, prepare the image to be bootable by e.g. 196eb8dc403SDave Cobbley creating and installing a bootloader configuration. 197eb8dc403SDave Cobbley """ 198eb8dc403SDave Cobbley source_plugin = self.ks.bootloader.source 199eb8dc403SDave Cobbley disk_name = self.parts[0].disk 200eb8dc403SDave Cobbley if source_plugin: 201eb8dc403SDave Cobbley plugin = PluginMgr.get_plugins('source')[source_plugin] 202eb8dc403SDave Cobbley plugin.do_install_disk(self._image, disk_name, self, self.workdir, 203eb8dc403SDave Cobbley self.oe_builddir, self.bootimg_dir, 204eb8dc403SDave Cobbley self.kernel_dir, self.native_sysroot) 205eb8dc403SDave Cobbley 206eb8dc403SDave Cobbley full_path = self._image.path 207eb8dc403SDave Cobbley # Generate .bmap 208eb8dc403SDave Cobbley if self.bmap: 209eb8dc403SDave Cobbley logger.debug("Generating bmap file for %s", disk_name) 210eb8dc403SDave Cobbley python = os.path.join(self.native_sysroot, 'usr/bin/python3-native/python3') 211eb8dc403SDave Cobbley bmaptool = os.path.join(self.native_sysroot, 'usr/bin/bmaptool') 212eb8dc403SDave Cobbley exec_native_cmd("%s %s create %s -o %s.bmap" % \ 213eb8dc403SDave Cobbley (python, bmaptool, full_path, full_path), self.native_sysroot) 214eb8dc403SDave Cobbley # Compress the image 215eb8dc403SDave Cobbley if self.compressor: 216eb8dc403SDave Cobbley logger.debug("Compressing disk %s with %s", disk_name, self.compressor) 217eb8dc403SDave Cobbley exec_cmd("%s %s" % (self.compressor, full_path)) 218eb8dc403SDave Cobbley 219eb8dc403SDave Cobbley def print_info(self): 220eb8dc403SDave Cobbley """ 221eb8dc403SDave Cobbley Print the image(s) and artifacts used, for the user. 222eb8dc403SDave Cobbley """ 223eb8dc403SDave Cobbley msg = "The new image(s) can be found here:\n" 224eb8dc403SDave Cobbley 225eb8dc403SDave Cobbley extension = "direct" + {"gzip": ".gz", 226eb8dc403SDave Cobbley "bzip2": ".bz2", 227eb8dc403SDave Cobbley "xz": ".xz", 228eb8dc403SDave Cobbley None: ""}.get(self.compressor) 229eb8dc403SDave Cobbley full_path = self._full_path(self.outdir, self.parts[0].disk, extension) 230eb8dc403SDave Cobbley msg += ' %s\n\n' % full_path 231eb8dc403SDave Cobbley 232eb8dc403SDave Cobbley msg += 'The following build artifacts were used to create the image(s):\n' 233eb8dc403SDave Cobbley for part in self.parts: 234eb8dc403SDave Cobbley if part.rootfs_dir is None: 235eb8dc403SDave Cobbley continue 236eb8dc403SDave Cobbley if part.mountpoint == '/': 237eb8dc403SDave Cobbley suffix = ':' 238eb8dc403SDave Cobbley else: 239eb8dc403SDave Cobbley suffix = '["%s"]:' % (part.mountpoint or part.label) 240eb8dc403SDave Cobbley rootdir = part.rootfs_dir 241eb8dc403SDave Cobbley msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), rootdir) 242eb8dc403SDave Cobbley 243eb8dc403SDave Cobbley msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir 244eb8dc403SDave Cobbley msg += ' KERNEL_DIR: %s\n' % self.kernel_dir 245eb8dc403SDave Cobbley msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot 246eb8dc403SDave Cobbley 247eb8dc403SDave Cobbley logger.info(msg) 248eb8dc403SDave Cobbley 249eb8dc403SDave Cobbley @property 250eb8dc403SDave Cobbley def rootdev(self): 251eb8dc403SDave Cobbley """ 252eb8dc403SDave Cobbley Get root device name to use as a 'root' parameter 253eb8dc403SDave Cobbley in kernel command line. 254eb8dc403SDave Cobbley 255eb8dc403SDave Cobbley Assume partition order same as in wks 256eb8dc403SDave Cobbley """ 257eb8dc403SDave Cobbley for part in self.parts: 258eb8dc403SDave Cobbley if part.mountpoint == "/": 259eb8dc403SDave Cobbley if part.uuid: 260eb8dc403SDave Cobbley return "PARTUUID=%s" % part.uuid 261eb8dc403SDave Cobbley else: 262eb8dc403SDave Cobbley suffix = 'p' if part.disk.startswith('mmcblk') else '' 263eb8dc403SDave Cobbley return "/dev/%s%s%-d" % (part.disk, suffix, part.realnum) 264eb8dc403SDave Cobbley 265eb8dc403SDave Cobbley def cleanup(self): 266eb8dc403SDave Cobbley if self._image: 267eb8dc403SDave Cobbley self._image.cleanup() 268eb8dc403SDave Cobbley 269eb8dc403SDave Cobbley # Move results to the output dir 270eb8dc403SDave Cobbley if not os.path.exists(self.outdir): 271eb8dc403SDave Cobbley os.makedirs(self.outdir) 272eb8dc403SDave Cobbley 273eb8dc403SDave Cobbley for fname in os.listdir(self.workdir): 274eb8dc403SDave Cobbley path = os.path.join(self.workdir, fname) 275eb8dc403SDave Cobbley if os.path.isfile(path): 276eb8dc403SDave Cobbley shutil.move(path, os.path.join(self.outdir, fname)) 277eb8dc403SDave Cobbley 278*d1e89497SAndrew Geissler # remove work directory when it is not in debugging mode 279*d1e89497SAndrew Geissler if not self.debug: 280eb8dc403SDave Cobbley shutil.rmtree(self.workdir, ignore_errors=True) 281eb8dc403SDave Cobbley 282eb8dc403SDave Cobbley# Overhead of the MBR partitioning scheme (just one sector) 283eb8dc403SDave CobbleyMBR_OVERHEAD = 1 284eb8dc403SDave Cobbley 285eb8dc403SDave Cobbley# Overhead of the GPT partitioning scheme 286eb8dc403SDave CobbleyGPT_OVERHEAD = 34 287eb8dc403SDave Cobbley 288eb8dc403SDave Cobbley# Size of a sector in bytes 289eb8dc403SDave CobbleySECTOR_SIZE = 512 290eb8dc403SDave Cobbley 291eb8dc403SDave Cobbleyclass PartitionedImage(): 292eb8dc403SDave Cobbley """ 293eb8dc403SDave Cobbley Partitioned image in a file. 294eb8dc403SDave Cobbley """ 295eb8dc403SDave Cobbley 296eb8dc403SDave Cobbley def __init__(self, path, ptable_format, partitions, native_sysroot=None): 297eb8dc403SDave Cobbley self.path = path # Path to the image file 298eb8dc403SDave Cobbley self.numpart = 0 # Number of allocated partitions 299eb8dc403SDave Cobbley self.realpart = 0 # Number of partitions in the partition table 30008902b01SBrad Bishop self.primary_part_num = 0 # Number of primary partitions (msdos) 30108902b01SBrad Bishop self.extendedpart = 0 # Create extended partition before this logical partition (msdos) 30208902b01SBrad Bishop self.extended_size_sec = 0 # Size of exteded partition (msdos) 30308902b01SBrad Bishop self.logical_part_cnt = 0 # Number of total logical paritions (msdos) 304eb8dc403SDave Cobbley self.offset = 0 # Offset of next partition (in sectors) 305eb8dc403SDave Cobbley self.min_size = 0 # Minimum required disk size to fit 306eb8dc403SDave Cobbley # all partitions (in bytes) 307eb8dc403SDave Cobbley self.ptable_format = ptable_format # Partition table format 308eb8dc403SDave Cobbley # Disk system identifier 309eb8dc403SDave Cobbley self.identifier = random.SystemRandom().randint(1, 0xffffffff) 310eb8dc403SDave Cobbley 311eb8dc403SDave Cobbley self.partitions = partitions 312eb8dc403SDave Cobbley self.partimages = [] 313eb8dc403SDave Cobbley # Size of a sector used in calculations 314eb8dc403SDave Cobbley self.sector_size = SECTOR_SIZE 315eb8dc403SDave Cobbley self.native_sysroot = native_sysroot 316f3f93bb8SBrad Bishop num_real_partitions = len([p for p in self.partitions if not p.no_table]) 317eb8dc403SDave Cobbley 318eb8dc403SDave Cobbley # calculate the real partition number, accounting for partitions not 319eb8dc403SDave Cobbley # in the partition table and logical partitions 320eb8dc403SDave Cobbley realnum = 0 321eb8dc403SDave Cobbley for part in self.partitions: 322eb8dc403SDave Cobbley if part.no_table: 323eb8dc403SDave Cobbley part.realnum = 0 324eb8dc403SDave Cobbley else: 325eb8dc403SDave Cobbley realnum += 1 326f3f93bb8SBrad Bishop if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4: 327eb8dc403SDave Cobbley part.realnum = realnum + 1 328eb8dc403SDave Cobbley continue 329eb8dc403SDave Cobbley part.realnum = realnum 330eb8dc403SDave Cobbley 331eb8dc403SDave Cobbley # generate parition and filesystem UUIDs 332eb8dc403SDave Cobbley for part in self.partitions: 333eb8dc403SDave Cobbley if not part.uuid and part.use_uuid: 334eb8dc403SDave Cobbley if self.ptable_format == 'gpt': 335eb8dc403SDave Cobbley part.uuid = str(uuid.uuid4()) 336eb8dc403SDave Cobbley else: # msdos partition table 337eb8dc403SDave Cobbley part.uuid = '%08x-%02d' % (self.identifier, part.realnum) 338eb8dc403SDave Cobbley if not part.fsuuid: 339eb8dc403SDave Cobbley if part.fstype == 'vfat' or part.fstype == 'msdos': 340eb8dc403SDave Cobbley part.fsuuid = '0x' + str(uuid.uuid4())[:8].upper() 341eb8dc403SDave Cobbley else: 342eb8dc403SDave Cobbley part.fsuuid = str(uuid.uuid4()) 343*d1e89497SAndrew Geissler else: 344*d1e89497SAndrew Geissler #make sure the fsuuid for vfat/msdos align with format 0xYYYYYYYY 345*d1e89497SAndrew Geissler if part.fstype == 'vfat' or part.fstype == 'msdos': 346*d1e89497SAndrew Geissler if part.fsuuid.upper().startswith("0X"): 347*d1e89497SAndrew Geissler part.fsuuid = '0x' + part.fsuuid.upper()[2:].rjust(8,"0") 348*d1e89497SAndrew Geissler else: 349*d1e89497SAndrew Geissler part.fsuuid = '0x' + part.fsuuid.upper().rjust(8,"0") 350eb8dc403SDave Cobbley 351eb8dc403SDave Cobbley def prepare(self, imager): 352eb8dc403SDave Cobbley """Prepare an image. Call prepare method of all image partitions.""" 353eb8dc403SDave Cobbley for part in self.partitions: 354eb8dc403SDave Cobbley # need to create the filesystems in order to get their 355eb8dc403SDave Cobbley # sizes before we can add them and do the layout. 356eb8dc403SDave Cobbley part.prepare(imager, imager.workdir, imager.oe_builddir, 357eb8dc403SDave Cobbley imager.rootfs_dir, imager.bootimg_dir, 358*d1e89497SAndrew Geissler imager.kernel_dir, imager.native_sysroot, 359*d1e89497SAndrew Geissler imager.updated_fstab_path) 360eb8dc403SDave Cobbley 361eb8dc403SDave Cobbley # Converting kB to sectors for parted 362eb8dc403SDave Cobbley part.size_sec = part.disk_size * 1024 // self.sector_size 363eb8dc403SDave Cobbley 364eb8dc403SDave Cobbley def layout_partitions(self): 365eb8dc403SDave Cobbley """ Layout the partitions, meaning calculate the position of every 366eb8dc403SDave Cobbley partition on the disk. The 'ptable_format' parameter defines the 367eb8dc403SDave Cobbley partition table format and may be "msdos". """ 368eb8dc403SDave Cobbley 369eb8dc403SDave Cobbley logger.debug("Assigning %s partitions to disks", self.ptable_format) 370eb8dc403SDave Cobbley 371eb8dc403SDave Cobbley # The number of primary and logical partitions. Extended partition and 372eb8dc403SDave Cobbley # partitions not listed in the table are not included. 373eb8dc403SDave Cobbley num_real_partitions = len([p for p in self.partitions if not p.no_table]) 374eb8dc403SDave Cobbley 375eb8dc403SDave Cobbley # Go through partitions in the order they are added in .ks file 376eb8dc403SDave Cobbley for num in range(len(self.partitions)): 377eb8dc403SDave Cobbley part = self.partitions[num] 378eb8dc403SDave Cobbley 379eb8dc403SDave Cobbley if self.ptable_format == 'msdos' and part.part_name: 380eb8dc403SDave Cobbley raise WicError("setting custom partition name is not " \ 381eb8dc403SDave Cobbley "implemented for msdos partitions") 382eb8dc403SDave Cobbley 383eb8dc403SDave Cobbley if self.ptable_format == 'msdos' and part.part_type: 384eb8dc403SDave Cobbley # The --part-type can also be implemented for MBR partitions, 385eb8dc403SDave Cobbley # in which case it would map to the 1-byte "partition type" 386eb8dc403SDave Cobbley # filed at offset 3 of the partition entry. 387eb8dc403SDave Cobbley raise WicError("setting custom partition type is not " \ 388eb8dc403SDave Cobbley "implemented for msdos partitions") 389eb8dc403SDave Cobbley 390eb8dc403SDave Cobbley # Get the disk where the partition is located 391eb8dc403SDave Cobbley self.numpart += 1 392eb8dc403SDave Cobbley if not part.no_table: 393eb8dc403SDave Cobbley self.realpart += 1 394eb8dc403SDave Cobbley 395eb8dc403SDave Cobbley if self.numpart == 1: 396eb8dc403SDave Cobbley if self.ptable_format == "msdos": 397eb8dc403SDave Cobbley overhead = MBR_OVERHEAD 398eb8dc403SDave Cobbley elif self.ptable_format == "gpt": 399eb8dc403SDave Cobbley overhead = GPT_OVERHEAD 400eb8dc403SDave Cobbley 401eb8dc403SDave Cobbley # Skip one sector required for the partitioning scheme overhead 402eb8dc403SDave Cobbley self.offset += overhead 403eb8dc403SDave Cobbley 40408902b01SBrad Bishop if self.ptable_format == "msdos": 40508902b01SBrad Bishop if self.primary_part_num > 3 or \ 40608902b01SBrad Bishop (self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4): 40708902b01SBrad Bishop part.type = 'logical' 408eb8dc403SDave Cobbley # Reserve a sector for EBR for every logical partition 409eb8dc403SDave Cobbley # before alignment is performed. 41008902b01SBrad Bishop if part.type == 'logical': 41182c905dcSAndrew Geissler self.offset += 2 412eb8dc403SDave Cobbley 41308902b01SBrad Bishop align_sectors = 0 414eb8dc403SDave Cobbley if part.align: 415eb8dc403SDave Cobbley # If not first partition and we do have alignment set we need 416eb8dc403SDave Cobbley # to align the partition. 417eb8dc403SDave Cobbley # FIXME: This leaves a empty spaces to the disk. To fill the 418eb8dc403SDave Cobbley # gaps we could enlargea the previous partition? 419eb8dc403SDave Cobbley 420eb8dc403SDave Cobbley # Calc how much the alignment is off. 421eb8dc403SDave Cobbley align_sectors = self.offset % (part.align * 1024 // self.sector_size) 422eb8dc403SDave Cobbley 423eb8dc403SDave Cobbley if align_sectors: 424eb8dc403SDave Cobbley # If partition is not aligned as required, we need 425eb8dc403SDave Cobbley # to move forward to the next alignment point 426eb8dc403SDave Cobbley align_sectors = (part.align * 1024 // self.sector_size) - align_sectors 427eb8dc403SDave Cobbley 428eb8dc403SDave Cobbley logger.debug("Realignment for %s%s with %s sectors, original" 429eb8dc403SDave Cobbley " offset %s, target alignment is %sK.", 430eb8dc403SDave Cobbley part.disk, self.numpart, align_sectors, 431eb8dc403SDave Cobbley self.offset, part.align) 432eb8dc403SDave Cobbley 433eb8dc403SDave Cobbley # increase the offset so we actually start the partition on right alignment 434eb8dc403SDave Cobbley self.offset += align_sectors 435eb8dc403SDave Cobbley 4364ed12e16SAndrew Geissler if part.offset is not None: 437c9f7865aSAndrew Geissler offset = part.offset // self.sector_size 4384ed12e16SAndrew Geissler 439c9f7865aSAndrew Geissler if offset * self.sector_size != part.offset: 440c9f7865aSAndrew Geissler raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size)) 4414ed12e16SAndrew Geissler 4424ed12e16SAndrew Geissler delta = offset - self.offset 4434ed12e16SAndrew Geissler if delta < 0: 444c9f7865aSAndrew 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)) 4454ed12e16SAndrew Geissler 4464ed12e16SAndrew Geissler logger.debug("Skipping %d sectors to place %s%s at offset %dK", 4474ed12e16SAndrew Geissler delta, part.disk, self.numpart, part.offset) 4484ed12e16SAndrew Geissler 4494ed12e16SAndrew Geissler self.offset = offset 4504ed12e16SAndrew Geissler 451eb8dc403SDave Cobbley part.start = self.offset 452eb8dc403SDave Cobbley self.offset += part.size_sec 453eb8dc403SDave Cobbley 454eb8dc403SDave Cobbley if not part.no_table: 455eb8dc403SDave Cobbley part.num = self.realpart 456eb8dc403SDave Cobbley else: 457eb8dc403SDave Cobbley part.num = 0 458eb8dc403SDave Cobbley 45908902b01SBrad Bishop if self.ptable_format == "msdos" and not part.no_table: 46008902b01SBrad Bishop if part.type == 'logical': 46108902b01SBrad Bishop self.logical_part_cnt += 1 46208902b01SBrad Bishop part.num = self.logical_part_cnt + 4 46308902b01SBrad Bishop if self.extendedpart == 0: 46408902b01SBrad Bishop # Create extended partition as a primary partition 46508902b01SBrad Bishop self.primary_part_num += 1 46608902b01SBrad Bishop self.extendedpart = part.num 46708902b01SBrad Bishop else: 46808902b01SBrad Bishop self.extended_size_sec += align_sectors 46982c905dcSAndrew Geissler self.extended_size_sec += part.size_sec + 2 47008902b01SBrad Bishop else: 47108902b01SBrad Bishop self.primary_part_num += 1 47208902b01SBrad Bishop part.num = self.primary_part_num 473eb8dc403SDave Cobbley 474eb8dc403SDave Cobbley logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " 475eb8dc403SDave Cobbley "sectors (%d bytes).", part.mountpoint, part.disk, 476eb8dc403SDave Cobbley part.num, part.start, self.offset - 1, part.size_sec, 477eb8dc403SDave Cobbley part.size_sec * self.sector_size) 478eb8dc403SDave Cobbley 479eb8dc403SDave Cobbley # Once all the partitions have been layed out, we can calculate the 480eb8dc403SDave Cobbley # minumim disk size 481eb8dc403SDave Cobbley self.min_size = self.offset 482eb8dc403SDave Cobbley if self.ptable_format == "gpt": 483eb8dc403SDave Cobbley self.min_size += GPT_OVERHEAD 484eb8dc403SDave Cobbley 485eb8dc403SDave Cobbley self.min_size *= self.sector_size 486eb8dc403SDave Cobbley 487eb8dc403SDave Cobbley def _create_partition(self, device, parttype, fstype, start, size): 488eb8dc403SDave Cobbley """ Create a partition on an image described by the 'device' object. """ 489eb8dc403SDave Cobbley 490eb8dc403SDave Cobbley # Start is included to the size so we need to substract one from the end. 491eb8dc403SDave Cobbley end = start + size - 1 492eb8dc403SDave Cobbley logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors", 493eb8dc403SDave Cobbley parttype, start, end, size) 494eb8dc403SDave Cobbley 495eb8dc403SDave Cobbley cmd = "parted -s %s unit s mkpart %s" % (device, parttype) 496eb8dc403SDave Cobbley if fstype: 497eb8dc403SDave Cobbley cmd += " %s" % fstype 498eb8dc403SDave Cobbley cmd += " %d %d" % (start, end) 499eb8dc403SDave Cobbley 500eb8dc403SDave Cobbley return exec_native_cmd(cmd, self.native_sysroot) 501eb8dc403SDave Cobbley 502eb8dc403SDave Cobbley def create(self): 503eb8dc403SDave Cobbley logger.debug("Creating sparse file %s", self.path) 504eb8dc403SDave Cobbley with open(self.path, 'w') as sparse: 505eb8dc403SDave Cobbley os.ftruncate(sparse.fileno(), self.min_size) 506eb8dc403SDave Cobbley 507eb8dc403SDave Cobbley logger.debug("Initializing partition table for %s", self.path) 508eb8dc403SDave Cobbley exec_native_cmd("parted -s %s mklabel %s" % 509eb8dc403SDave Cobbley (self.path, self.ptable_format), self.native_sysroot) 510eb8dc403SDave Cobbley 511eb8dc403SDave Cobbley logger.debug("Set disk identifier %x", self.identifier) 512eb8dc403SDave Cobbley with open(self.path, 'r+b') as img: 513eb8dc403SDave Cobbley img.seek(0x1B8) 514eb8dc403SDave Cobbley img.write(self.identifier.to_bytes(4, 'little')) 515eb8dc403SDave Cobbley 516eb8dc403SDave Cobbley logger.debug("Creating partitions") 517eb8dc403SDave Cobbley 518eb8dc403SDave Cobbley for part in self.partitions: 519eb8dc403SDave Cobbley if part.num == 0: 520eb8dc403SDave Cobbley continue 521eb8dc403SDave Cobbley 52208902b01SBrad Bishop if self.ptable_format == "msdos" and part.num == self.extendedpart: 523eb8dc403SDave Cobbley # Create an extended partition (note: extended 524eb8dc403SDave Cobbley # partition is described in MBR and contains all 525eb8dc403SDave Cobbley # logical partitions). The logical partitions save a 526eb8dc403SDave Cobbley # sector for an EBR just before the start of a 527eb8dc403SDave Cobbley # partition. The extended partition must start one 528eb8dc403SDave Cobbley # sector before the start of the first logical 529eb8dc403SDave Cobbley # partition. This way the first EBR is inside of the 530eb8dc403SDave Cobbley # extended partition. Since the extended partitions 531eb8dc403SDave Cobbley # starts a sector before the first logical partition, 532eb8dc403SDave Cobbley # add a sector at the back, so that there is enough 533eb8dc403SDave Cobbley # room for all logical partitions. 534eb8dc403SDave Cobbley self._create_partition(self.path, "extended", 53582c905dcSAndrew Geissler None, part.start - 2, 53608902b01SBrad Bishop self.extended_size_sec) 537eb8dc403SDave Cobbley 538eb8dc403SDave Cobbley if part.fstype == "swap": 539eb8dc403SDave Cobbley parted_fs_type = "linux-swap" 540eb8dc403SDave Cobbley elif part.fstype == "vfat": 541eb8dc403SDave Cobbley parted_fs_type = "fat32" 542eb8dc403SDave Cobbley elif part.fstype == "msdos": 543eb8dc403SDave Cobbley parted_fs_type = "fat16" 544eb8dc403SDave Cobbley if not part.system_id: 545eb8dc403SDave Cobbley part.system_id = '0x6' # FAT16 546eb8dc403SDave Cobbley else: 547eb8dc403SDave Cobbley # Type for ext2/ext3/ext4/btrfs 548eb8dc403SDave Cobbley parted_fs_type = "ext2" 549eb8dc403SDave Cobbley 550eb8dc403SDave Cobbley # Boot ROM of OMAP boards require vfat boot partition to have an 551eb8dc403SDave Cobbley # even number of sectors. 552eb8dc403SDave Cobbley if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \ 553eb8dc403SDave Cobbley and part.size_sec % 2: 554eb8dc403SDave Cobbley logger.debug("Subtracting one sector from '%s' partition to " 555eb8dc403SDave Cobbley "get even number of sectors for the partition", 556eb8dc403SDave Cobbley part.mountpoint) 557eb8dc403SDave Cobbley part.size_sec -= 1 558eb8dc403SDave Cobbley 559eb8dc403SDave Cobbley self._create_partition(self.path, part.type, 560eb8dc403SDave Cobbley parted_fs_type, part.start, part.size_sec) 561eb8dc403SDave Cobbley 562eb8dc403SDave Cobbley if part.part_name: 563eb8dc403SDave Cobbley logger.debug("partition %d: set name to %s", 564eb8dc403SDave Cobbley part.num, part.part_name) 565eb8dc403SDave Cobbley exec_native_cmd("sgdisk --change-name=%d:%s %s" % \ 566eb8dc403SDave Cobbley (part.num, part.part_name, 567eb8dc403SDave Cobbley self.path), self.native_sysroot) 568eb8dc403SDave Cobbley 569eb8dc403SDave Cobbley if part.part_type: 570eb8dc403SDave Cobbley logger.debug("partition %d: set type UID to %s", 571eb8dc403SDave Cobbley part.num, part.part_type) 572eb8dc403SDave Cobbley exec_native_cmd("sgdisk --typecode=%d:%s %s" % \ 573eb8dc403SDave Cobbley (part.num, part.part_type, 574eb8dc403SDave Cobbley self.path), self.native_sysroot) 575eb8dc403SDave Cobbley 576eb8dc403SDave Cobbley if part.uuid and self.ptable_format == "gpt": 577eb8dc403SDave Cobbley logger.debug("partition %d: set UUID to %s", 578eb8dc403SDave Cobbley part.num, part.uuid) 579eb8dc403SDave Cobbley exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \ 580eb8dc403SDave Cobbley (part.num, part.uuid, self.path), 581eb8dc403SDave Cobbley self.native_sysroot) 582eb8dc403SDave Cobbley 583eb8dc403SDave Cobbley if part.label and self.ptable_format == "gpt": 584eb8dc403SDave Cobbley logger.debug("partition %d: set name to %s", 585eb8dc403SDave Cobbley part.num, part.label) 586eb8dc403SDave Cobbley exec_native_cmd("parted -s %s name %d %s" % \ 587eb8dc403SDave Cobbley (self.path, part.num, part.label), 588eb8dc403SDave Cobbley self.native_sysroot) 589eb8dc403SDave Cobbley 590eb8dc403SDave Cobbley if part.active: 591eb8dc403SDave Cobbley flag_name = "legacy_boot" if self.ptable_format == 'gpt' else "boot" 592eb8dc403SDave Cobbley logger.debug("Set '%s' flag for partition '%s' on disk '%s'", 593eb8dc403SDave Cobbley flag_name, part.num, self.path) 594eb8dc403SDave Cobbley exec_native_cmd("parted -s %s set %d %s on" % \ 595eb8dc403SDave Cobbley (self.path, part.num, flag_name), 596eb8dc403SDave Cobbley self.native_sysroot) 597eb8dc403SDave Cobbley if part.system_id: 598eb8dc403SDave Cobbley exec_native_cmd("sfdisk --part-type %s %s %s" % \ 599eb8dc403SDave Cobbley (self.path, part.num, part.system_id), 600eb8dc403SDave Cobbley self.native_sysroot) 601eb8dc403SDave Cobbley 602eb8dc403SDave Cobbley def cleanup(self): 60382c905dcSAndrew Geissler pass 604eb8dc403SDave Cobbley 605eb8dc403SDave Cobbley def assemble(self): 606eb8dc403SDave Cobbley logger.debug("Installing partitions") 607eb8dc403SDave Cobbley 608eb8dc403SDave Cobbley for part in self.partitions: 609eb8dc403SDave Cobbley source = part.source_file 610eb8dc403SDave Cobbley if source: 611eb8dc403SDave Cobbley # install source_file contents into a partition 612eb8dc403SDave Cobbley sparse_copy(source, self.path, seek=part.start * self.sector_size) 613eb8dc403SDave Cobbley 614eb8dc403SDave Cobbley logger.debug("Installed %s in partition %d, sectors %d-%d, " 615eb8dc403SDave Cobbley "size %d sectors", source, part.num, part.start, 616eb8dc403SDave Cobbley part.start + part.size_sec - 1, part.size_sec) 617eb8dc403SDave Cobbley 618eb8dc403SDave Cobbley partimage = self.path + '.p%d' % part.num 619eb8dc403SDave Cobbley os.rename(source, partimage) 620eb8dc403SDave Cobbley self.partimages.append(partimage) 621