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