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 8eb8dc403SDave Cobbley# This module implements the image creation engine used by 'wic' to 9eb8dc403SDave Cobbley# create images. The engine parses through the OpenEmbedded kickstart 10eb8dc403SDave Cobbley# (wks) file specified and generates images that can then be directly 11eb8dc403SDave Cobbley# written onto media. 12eb8dc403SDave Cobbley# 13eb8dc403SDave Cobbley# AUTHORS 14eb8dc403SDave Cobbley# Tom Zanussi <tom.zanussi (at] linux.intel.com> 15eb8dc403SDave Cobbley# 16eb8dc403SDave Cobbley 17eb8dc403SDave Cobbleyimport logging 18eb8dc403SDave Cobbleyimport os 19eb8dc403SDave Cobbleyimport tempfile 20eb8dc403SDave Cobbleyimport json 21eb8dc403SDave Cobbleyimport subprocess 22*595f6308SAndrew Geisslerimport shutil 236dbb316aSBrad Bishopimport re 24eb8dc403SDave Cobbley 25eb8dc403SDave Cobbleyfrom collections import namedtuple, OrderedDict 26eb8dc403SDave Cobbley 27eb8dc403SDave Cobbleyfrom wic import WicError 28eb8dc403SDave Cobbleyfrom wic.filemap import sparse_copy 29eb8dc403SDave Cobbleyfrom wic.pluginbase import PluginMgr 30eb8dc403SDave Cobbleyfrom wic.misc import get_bitbake_var, exec_cmd 31eb8dc403SDave Cobbley 32eb8dc403SDave Cobbleylogger = logging.getLogger('wic') 33eb8dc403SDave Cobbley 34eb8dc403SDave Cobbleydef verify_build_env(): 35eb8dc403SDave Cobbley """ 36eb8dc403SDave Cobbley Verify that the build environment is sane. 37eb8dc403SDave Cobbley 38eb8dc403SDave Cobbley Returns True if it is, false otherwise 39eb8dc403SDave Cobbley """ 40eb8dc403SDave Cobbley if not os.environ.get("BUILDDIR"): 41eb8dc403SDave Cobbley raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)") 42eb8dc403SDave Cobbley 43eb8dc403SDave Cobbley return True 44eb8dc403SDave Cobbley 45eb8dc403SDave Cobbley 46eb8dc403SDave CobbleyCANNED_IMAGE_DIR = "lib/wic/canned-wks" # relative to scripts 47eb8dc403SDave CobbleySCRIPTS_CANNED_IMAGE_DIR = "scripts/" + CANNED_IMAGE_DIR 48eb8dc403SDave CobbleyWIC_DIR = "wic" 49eb8dc403SDave Cobbley 50eb8dc403SDave Cobbleydef build_canned_image_list(path): 51eb8dc403SDave Cobbley layers_path = get_bitbake_var("BBLAYERS") 52eb8dc403SDave Cobbley canned_wks_layer_dirs = [] 53eb8dc403SDave Cobbley 54eb8dc403SDave Cobbley if layers_path is not None: 55eb8dc403SDave Cobbley for layer_path in layers_path.split(): 56eb8dc403SDave Cobbley for wks_path in (WIC_DIR, SCRIPTS_CANNED_IMAGE_DIR): 57eb8dc403SDave Cobbley cpath = os.path.join(layer_path, wks_path) 58eb8dc403SDave Cobbley if os.path.isdir(cpath): 59eb8dc403SDave Cobbley canned_wks_layer_dirs.append(cpath) 60eb8dc403SDave Cobbley 61eb8dc403SDave Cobbley cpath = os.path.join(path, CANNED_IMAGE_DIR) 62eb8dc403SDave Cobbley canned_wks_layer_dirs.append(cpath) 63eb8dc403SDave Cobbley 64eb8dc403SDave Cobbley return canned_wks_layer_dirs 65eb8dc403SDave Cobbley 66eb8dc403SDave Cobbleydef find_canned_image(scripts_path, wks_file): 67eb8dc403SDave Cobbley """ 68eb8dc403SDave Cobbley Find a .wks file with the given name in the canned files dir. 69eb8dc403SDave Cobbley 70eb8dc403SDave Cobbley Return False if not found 71eb8dc403SDave Cobbley """ 72eb8dc403SDave Cobbley layers_canned_wks_dir = build_canned_image_list(scripts_path) 73eb8dc403SDave Cobbley 74eb8dc403SDave Cobbley for canned_wks_dir in layers_canned_wks_dir: 75eb8dc403SDave Cobbley for root, dirs, files in os.walk(canned_wks_dir): 76eb8dc403SDave Cobbley for fname in files: 77eb8dc403SDave Cobbley if fname.endswith("~") or fname.endswith("#"): 78eb8dc403SDave Cobbley continue 7915ae2509SBrad Bishop if ((fname.endswith(".wks") and wks_file + ".wks" == fname) or \ 8015ae2509SBrad Bishop (fname.endswith(".wks.in") and wks_file + ".wks.in" == fname)): 81eb8dc403SDave Cobbley fullpath = os.path.join(canned_wks_dir, fname) 82eb8dc403SDave Cobbley return fullpath 83eb8dc403SDave Cobbley return None 84eb8dc403SDave Cobbley 85eb8dc403SDave Cobbley 86eb8dc403SDave Cobbleydef list_canned_images(scripts_path): 87eb8dc403SDave Cobbley """ 88eb8dc403SDave Cobbley List the .wks files in the canned image dir, minus the extension. 89eb8dc403SDave Cobbley """ 90eb8dc403SDave Cobbley layers_canned_wks_dir = build_canned_image_list(scripts_path) 91eb8dc403SDave Cobbley 92eb8dc403SDave Cobbley for canned_wks_dir in layers_canned_wks_dir: 93eb8dc403SDave Cobbley for root, dirs, files in os.walk(canned_wks_dir): 94eb8dc403SDave Cobbley for fname in files: 95eb8dc403SDave Cobbley if fname.endswith("~") or fname.endswith("#"): 96eb8dc403SDave Cobbley continue 9715ae2509SBrad Bishop if fname.endswith(".wks") or fname.endswith(".wks.in"): 98eb8dc403SDave Cobbley fullpath = os.path.join(canned_wks_dir, fname) 99eb8dc403SDave Cobbley with open(fullpath) as wks: 100eb8dc403SDave Cobbley for line in wks: 101eb8dc403SDave Cobbley desc = "" 102eb8dc403SDave Cobbley idx = line.find("short-description:") 103eb8dc403SDave Cobbley if idx != -1: 104eb8dc403SDave Cobbley desc = line[idx + len("short-description:"):].strip() 105eb8dc403SDave Cobbley break 10615ae2509SBrad Bishop basename = fname.split('.')[0] 107eb8dc403SDave Cobbley print(" %s\t\t%s" % (basename.ljust(30), desc)) 108eb8dc403SDave Cobbley 109eb8dc403SDave Cobbley 110eb8dc403SDave Cobbleydef list_canned_image_help(scripts_path, fullpath): 111eb8dc403SDave Cobbley """ 112eb8dc403SDave Cobbley List the help and params in the specified canned image. 113eb8dc403SDave Cobbley """ 114eb8dc403SDave Cobbley found = False 115eb8dc403SDave Cobbley with open(fullpath) as wks: 116eb8dc403SDave Cobbley for line in wks: 117eb8dc403SDave Cobbley if not found: 118eb8dc403SDave Cobbley idx = line.find("long-description:") 119eb8dc403SDave Cobbley if idx != -1: 120eb8dc403SDave Cobbley print() 121eb8dc403SDave Cobbley print(line[idx + len("long-description:"):].strip()) 122eb8dc403SDave Cobbley found = True 123eb8dc403SDave Cobbley continue 124eb8dc403SDave Cobbley if not line.strip(): 125eb8dc403SDave Cobbley break 126eb8dc403SDave Cobbley idx = line.find("#") 127eb8dc403SDave Cobbley if idx != -1: 128eb8dc403SDave Cobbley print(line[idx + len("#:"):].rstrip()) 129eb8dc403SDave Cobbley else: 130eb8dc403SDave Cobbley break 131eb8dc403SDave Cobbley 132eb8dc403SDave Cobbley 133eb8dc403SDave Cobbleydef list_source_plugins(): 134eb8dc403SDave Cobbley """ 135eb8dc403SDave Cobbley List the available source plugins i.e. plugins available for --source. 136eb8dc403SDave Cobbley """ 137eb8dc403SDave Cobbley plugins = PluginMgr.get_plugins('source') 138eb8dc403SDave Cobbley 139eb8dc403SDave Cobbley for plugin in plugins: 140eb8dc403SDave Cobbley print(" %s" % plugin) 141eb8dc403SDave Cobbley 142eb8dc403SDave Cobbley 143eb8dc403SDave Cobbleydef wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir, 144eb8dc403SDave Cobbley native_sysroot, options): 145eb8dc403SDave Cobbley """ 146eb8dc403SDave Cobbley Create image 147eb8dc403SDave Cobbley 148eb8dc403SDave Cobbley wks_file - user-defined OE kickstart file 149eb8dc403SDave Cobbley rootfs_dir - absolute path to the build's /rootfs dir 150eb8dc403SDave Cobbley bootimg_dir - absolute path to the build's boot artifacts directory 151eb8dc403SDave Cobbley kernel_dir - absolute path to the build's kernel directory 152eb8dc403SDave Cobbley native_sysroot - absolute path to the build's native sysroots dir 153eb8dc403SDave Cobbley image_output_dir - dirname to create for image 154eb8dc403SDave Cobbley options - wic command line options (debug, bmap, etc) 155eb8dc403SDave Cobbley 156eb8dc403SDave Cobbley Normally, the values for the build artifacts values are determined 157eb8dc403SDave Cobbley by 'wic -e' from the output of the 'bitbake -e' command given an 158eb8dc403SDave Cobbley image name e.g. 'core-image-minimal' and a given machine set in 159eb8dc403SDave Cobbley local.conf. If that's the case, the variables get the following 160eb8dc403SDave Cobbley values from the output of 'bitbake -e': 161eb8dc403SDave Cobbley 162eb8dc403SDave Cobbley rootfs_dir: IMAGE_ROOTFS 163eb8dc403SDave Cobbley kernel_dir: DEPLOY_DIR_IMAGE 164eb8dc403SDave Cobbley native_sysroot: STAGING_DIR_NATIVE 165eb8dc403SDave Cobbley 166eb8dc403SDave Cobbley In the above case, bootimg_dir remains unset and the 167eb8dc403SDave Cobbley plugin-specific image creation code is responsible for finding the 168eb8dc403SDave Cobbley bootimg artifacts. 169eb8dc403SDave Cobbley 170eb8dc403SDave Cobbley In the case where the values are passed in explicitly i.e 'wic -e' 171eb8dc403SDave Cobbley is not used but rather the individual 'wic' options are used to 172eb8dc403SDave Cobbley explicitly specify these values. 173eb8dc403SDave Cobbley """ 174eb8dc403SDave Cobbley try: 175eb8dc403SDave Cobbley oe_builddir = os.environ["BUILDDIR"] 176eb8dc403SDave Cobbley except KeyError: 177eb8dc403SDave Cobbley raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)") 178eb8dc403SDave Cobbley 179eb8dc403SDave Cobbley if not os.path.exists(options.outdir): 180eb8dc403SDave Cobbley os.makedirs(options.outdir) 181eb8dc403SDave Cobbley 1821a4b7ee2SBrad Bishop pname = options.imager 183eb8dc403SDave Cobbley plugin_class = PluginMgr.get_plugins('imager').get(pname) 184eb8dc403SDave Cobbley if not plugin_class: 185eb8dc403SDave Cobbley raise WicError('Unknown plugin: %s' % pname) 186eb8dc403SDave Cobbley 187eb8dc403SDave Cobbley plugin = plugin_class(wks_file, rootfs_dir, bootimg_dir, kernel_dir, 188eb8dc403SDave Cobbley native_sysroot, oe_builddir, options) 189eb8dc403SDave Cobbley 190eb8dc403SDave Cobbley plugin.do_create() 191eb8dc403SDave Cobbley 192eb8dc403SDave Cobbley logger.info("The image(s) were created using OE kickstart file:\n %s", wks_file) 193eb8dc403SDave Cobbley 194eb8dc403SDave Cobbley 195eb8dc403SDave Cobbleydef wic_list(args, scripts_path): 196eb8dc403SDave Cobbley """ 197eb8dc403SDave Cobbley Print the list of images or source plugins. 198eb8dc403SDave Cobbley """ 199eb8dc403SDave Cobbley if args.list_type is None: 200eb8dc403SDave Cobbley return False 201eb8dc403SDave Cobbley 202eb8dc403SDave Cobbley if args.list_type == "images": 203eb8dc403SDave Cobbley 204eb8dc403SDave Cobbley list_canned_images(scripts_path) 205eb8dc403SDave Cobbley return True 206eb8dc403SDave Cobbley elif args.list_type == "source-plugins": 207eb8dc403SDave Cobbley list_source_plugins() 208eb8dc403SDave Cobbley return True 209eb8dc403SDave Cobbley elif len(args.help_for) == 1 and args.help_for[0] == 'help': 210eb8dc403SDave Cobbley wks_file = args.list_type 211eb8dc403SDave Cobbley fullpath = find_canned_image(scripts_path, wks_file) 212eb8dc403SDave Cobbley if not fullpath: 213eb8dc403SDave Cobbley raise WicError("No image named %s found, exiting. " 214eb8dc403SDave Cobbley "(Use 'wic list images' to list available images, " 215eb8dc403SDave Cobbley "or specify a fully-qualified OE kickstart (.wks) " 216eb8dc403SDave Cobbley "filename)" % wks_file) 217eb8dc403SDave Cobbley 218eb8dc403SDave Cobbley list_canned_image_help(scripts_path, fullpath) 219eb8dc403SDave Cobbley return True 220eb8dc403SDave Cobbley 221eb8dc403SDave Cobbley return False 222eb8dc403SDave Cobbley 223eb8dc403SDave Cobbley 224eb8dc403SDave Cobbleyclass Disk: 225eb8dc403SDave Cobbley def __init__(self, imagepath, native_sysroot, fstypes=('fat', 'ext')): 226eb8dc403SDave Cobbley self.imagepath = imagepath 227eb8dc403SDave Cobbley self.native_sysroot = native_sysroot 228eb8dc403SDave Cobbley self.fstypes = fstypes 229eb8dc403SDave Cobbley self._partitions = None 230eb8dc403SDave Cobbley self._partimages = {} 231eb8dc403SDave Cobbley self._lsector_size = None 232eb8dc403SDave Cobbley self._psector_size = None 233eb8dc403SDave Cobbley self._ptable_format = None 234eb8dc403SDave Cobbley 235eb8dc403SDave Cobbley # find parted 23699467dabSAndrew Geissler # read paths from $PATH environment variable 23799467dabSAndrew Geissler # if it fails, use hardcoded paths 23899467dabSAndrew Geissler pathlist = "/bin:/usr/bin:/usr/sbin:/sbin/" 23999467dabSAndrew Geissler try: 24099467dabSAndrew Geissler self.paths = os.environ['PATH'] + ":" + pathlist 24199467dabSAndrew Geissler except KeyError: 24299467dabSAndrew Geissler self.paths = pathlist 24399467dabSAndrew Geissler 244eb8dc403SDave Cobbley if native_sysroot: 24599467dabSAndrew Geissler for path in pathlist.split(':'): 246eb8dc403SDave Cobbley self.paths = "%s%s:%s" % (native_sysroot, path, self.paths) 247eb8dc403SDave Cobbley 248*595f6308SAndrew Geissler self.parted = shutil.which("parted", path=self.paths) 249eb8dc403SDave Cobbley if not self.parted: 250eb8dc403SDave Cobbley raise WicError("Can't find executable parted") 251eb8dc403SDave Cobbley 252eb8dc403SDave Cobbley self.partitions = self.get_partitions() 253eb8dc403SDave Cobbley 254eb8dc403SDave Cobbley def __del__(self): 255eb8dc403SDave Cobbley for path in self._partimages.values(): 256eb8dc403SDave Cobbley os.unlink(path) 257eb8dc403SDave Cobbley 258eb8dc403SDave Cobbley def get_partitions(self): 259eb8dc403SDave Cobbley if self._partitions is None: 260eb8dc403SDave Cobbley self._partitions = OrderedDict() 261eb8dc403SDave Cobbley out = exec_cmd("%s -sm %s unit B print" % (self.parted, self.imagepath)) 262eb8dc403SDave Cobbley parttype = namedtuple("Part", "pnum start end size fstype") 263eb8dc403SDave Cobbley splitted = out.splitlines() 2641a4b7ee2SBrad Bishop # skip over possible errors in exec_cmd output 2651a4b7ee2SBrad Bishop try: 2661a4b7ee2SBrad Bishop idx =splitted.index("BYT;") 2671a4b7ee2SBrad Bishop except ValueError: 2681a4b7ee2SBrad Bishop raise WicError("Error getting partition information from %s" % (self.parted)) 2691a4b7ee2SBrad Bishop lsector_size, psector_size, self._ptable_format = splitted[idx + 1].split(":")[3:6] 270eb8dc403SDave Cobbley self._lsector_size = int(lsector_size) 271eb8dc403SDave Cobbley self._psector_size = int(psector_size) 2721a4b7ee2SBrad Bishop for line in splitted[idx + 2:]: 273eb8dc403SDave Cobbley pnum, start, end, size, fstype = line.split(':')[:5] 274eb8dc403SDave Cobbley partition = parttype(int(pnum), int(start[:-1]), int(end[:-1]), 275eb8dc403SDave Cobbley int(size[:-1]), fstype) 276eb8dc403SDave Cobbley self._partitions[pnum] = partition 277eb8dc403SDave Cobbley 278eb8dc403SDave Cobbley return self._partitions 279eb8dc403SDave Cobbley 280eb8dc403SDave Cobbley def __getattr__(self, name): 281eb8dc403SDave Cobbley """Get path to the executable in a lazy way.""" 282eb8dc403SDave Cobbley if name in ("mdir", "mcopy", "mdel", "mdeltree", "sfdisk", "e2fsck", 28382c905dcSAndrew Geissler "resize2fs", "mkswap", "mkdosfs", "debugfs","blkid"): 284eb8dc403SDave Cobbley aname = "_%s" % name 285eb8dc403SDave Cobbley if aname not in self.__dict__: 286*595f6308SAndrew Geissler setattr(self, aname, shutil.which(name, path=self.paths)) 287d5ae7d90SBrad Bishop if aname not in self.__dict__ or self.__dict__[aname] is None: 288d5ae7d90SBrad Bishop raise WicError("Can't find executable '{}'".format(name)) 289eb8dc403SDave Cobbley return self.__dict__[aname] 290eb8dc403SDave Cobbley return self.__dict__[name] 291eb8dc403SDave Cobbley 292eb8dc403SDave Cobbley def _get_part_image(self, pnum): 293eb8dc403SDave Cobbley if pnum not in self.partitions: 29482c905dcSAndrew Geissler raise WicError("Partition %s is not in the image" % pnum) 295eb8dc403SDave Cobbley part = self.partitions[pnum] 296eb8dc403SDave Cobbley # check if fstype is supported 297eb8dc403SDave Cobbley for fstype in self.fstypes: 298eb8dc403SDave Cobbley if part.fstype.startswith(fstype): 299eb8dc403SDave Cobbley break 300eb8dc403SDave Cobbley else: 301eb8dc403SDave Cobbley raise WicError("Not supported fstype: {}".format(part.fstype)) 302eb8dc403SDave Cobbley if pnum not in self._partimages: 303eb8dc403SDave Cobbley tmpf = tempfile.NamedTemporaryFile(prefix="wic-part") 304eb8dc403SDave Cobbley dst_fname = tmpf.name 305eb8dc403SDave Cobbley tmpf.close() 306eb8dc403SDave Cobbley sparse_copy(self.imagepath, dst_fname, skip=part.start, length=part.size) 307eb8dc403SDave Cobbley self._partimages[pnum] = dst_fname 308eb8dc403SDave Cobbley 309eb8dc403SDave Cobbley return self._partimages[pnum] 310eb8dc403SDave Cobbley 311eb8dc403SDave Cobbley def _put_part_image(self, pnum): 312eb8dc403SDave Cobbley """Put partition image into partitioned image.""" 313eb8dc403SDave Cobbley sparse_copy(self._partimages[pnum], self.imagepath, 314eb8dc403SDave Cobbley seek=self.partitions[pnum].start) 315eb8dc403SDave Cobbley 316eb8dc403SDave Cobbley def dir(self, pnum, path): 31782c905dcSAndrew Geissler if pnum not in self.partitions: 31882c905dcSAndrew Geissler raise WicError("Partition %s is not in the image" % pnum) 31982c905dcSAndrew Geissler 320eb8dc403SDave Cobbley if self.partitions[pnum].fstype.startswith('ext'): 321eb8dc403SDave Cobbley return exec_cmd("{} {} -R 'ls -l {}'".format(self.debugfs, 322eb8dc403SDave Cobbley self._get_part_image(pnum), 323eb8dc403SDave Cobbley path), as_shell=True) 324eb8dc403SDave Cobbley else: # fat 325eb8dc403SDave Cobbley return exec_cmd("{} -i {} ::{}".format(self.mdir, 326eb8dc403SDave Cobbley self._get_part_image(pnum), 327eb8dc403SDave Cobbley path)) 328eb8dc403SDave Cobbley 32982c905dcSAndrew Geissler def copy(self, src, dest): 330eb8dc403SDave Cobbley """Copy partition image into wic image.""" 33182c905dcSAndrew Geissler pnum = dest.part if isinstance(src, str) else src.part 33282c905dcSAndrew Geissler 333eb8dc403SDave Cobbley if self.partitions[pnum].fstype.startswith('ext'): 33482c905dcSAndrew Geissler if isinstance(src, str): 33519323693SBrad Bishop cmd = "printf 'cd {}\nwrite {} {}\n' | {} -w {}".\ 33682c905dcSAndrew Geissler format(os.path.dirname(dest.path), src, os.path.basename(src), 337eb8dc403SDave Cobbley self.debugfs, self._get_part_image(pnum)) 33882c905dcSAndrew Geissler else: # copy from wic 33982c905dcSAndrew Geissler # run both dump and rdump to support both files and directory 34082c905dcSAndrew Geissler cmd = "printf 'cd {}\ndump /{} {}\nrdump /{} {}\n' | {} {}".\ 34182c905dcSAndrew Geissler format(os.path.dirname(src.path), src.path, 34282c905dcSAndrew Geissler dest, src.path, dest, self.debugfs, 34382c905dcSAndrew Geissler self._get_part_image(pnum)) 344eb8dc403SDave Cobbley else: # fat 34582c905dcSAndrew Geissler if isinstance(src, str): 346eb8dc403SDave Cobbley cmd = "{} -i {} -snop {} ::{}".format(self.mcopy, 347eb8dc403SDave Cobbley self._get_part_image(pnum), 34882c905dcSAndrew Geissler src, dest.path) 34982c905dcSAndrew Geissler else: 35082c905dcSAndrew Geissler cmd = "{} -i {} -snop ::{} {}".format(self.mcopy, 35182c905dcSAndrew Geissler self._get_part_image(pnum), 35282c905dcSAndrew Geissler src.path, dest) 35382c905dcSAndrew Geissler 354eb8dc403SDave Cobbley exec_cmd(cmd, as_shell=True) 355eb8dc403SDave Cobbley self._put_part_image(pnum) 356eb8dc403SDave Cobbley 3576dbb316aSBrad Bishop def remove_ext(self, pnum, path, recursive): 3586dbb316aSBrad Bishop """ 3596dbb316aSBrad Bishop Remove files/dirs and their contents from the partition. 3606dbb316aSBrad Bishop This only applies to ext* partition. 3616dbb316aSBrad Bishop """ 3626dbb316aSBrad Bishop abs_path = re.sub('\/\/+', '/', path) 3636dbb316aSBrad Bishop cmd = "{} {} -wR 'rm \"{}\"'".format(self.debugfs, 364eb8dc403SDave Cobbley self._get_part_image(pnum), 3656dbb316aSBrad Bishop abs_path) 3661a4b7ee2SBrad Bishop out = exec_cmd(cmd , as_shell=True) 3671a4b7ee2SBrad Bishop for line in out.splitlines(): 3681a4b7ee2SBrad Bishop if line.startswith("rm:"): 3691a4b7ee2SBrad Bishop if "file is a directory" in line: 3706dbb316aSBrad Bishop if recursive: 3716dbb316aSBrad Bishop # loop through content and delete them one by one if 3726dbb316aSBrad Bishop # flaged with -r 3736dbb316aSBrad Bishop subdirs = iter(self.dir(pnum, abs_path).splitlines()) 3746dbb316aSBrad Bishop next(subdirs) 3756dbb316aSBrad Bishop for subdir in subdirs: 3766dbb316aSBrad Bishop dir = subdir.split(':')[1].split(" ", 1)[1] 3776dbb316aSBrad Bishop if not dir == "." and not dir == "..": 3786dbb316aSBrad Bishop self.remove_ext(pnum, "%s/%s" % (abs_path, dir), recursive) 3796dbb316aSBrad Bishop 3806dbb316aSBrad Bishop rmdir_out = exec_cmd("{} {} -wR 'rmdir \"{}\"'".format(self.debugfs, 3811a4b7ee2SBrad Bishop self._get_part_image(pnum), 3826dbb316aSBrad Bishop abs_path.rstrip('/')) 3836dbb316aSBrad Bishop , as_shell=True) 3846dbb316aSBrad Bishop 3856dbb316aSBrad Bishop for rmdir_line in rmdir_out.splitlines(): 3866dbb316aSBrad Bishop if "directory not empty" in rmdir_line: 3876dbb316aSBrad Bishop raise WicError("Could not complete operation: \n%s \n" 3886dbb316aSBrad Bishop "use -r to remove non-empty directory" % rmdir_line) 3896dbb316aSBrad Bishop if rmdir_line.startswith("rmdir:"): 3906dbb316aSBrad Bishop raise WicError("Could not complete operation: \n%s " 3916dbb316aSBrad Bishop "\n%s" % (str(line), rmdir_line)) 3926dbb316aSBrad Bishop 3931a4b7ee2SBrad Bishop else: 3946dbb316aSBrad Bishop raise WicError("Could not complete operation: \n%s " 3956dbb316aSBrad Bishop "\nUnable to remove %s" % (str(line), abs_path)) 3966dbb316aSBrad Bishop 3976dbb316aSBrad Bishop def remove(self, pnum, path, recursive): 3986dbb316aSBrad Bishop """Remove files/dirs from the partition.""" 3996dbb316aSBrad Bishop partimg = self._get_part_image(pnum) 4006dbb316aSBrad Bishop if self.partitions[pnum].fstype.startswith('ext'): 4016dbb316aSBrad Bishop self.remove_ext(pnum, path, recursive) 4026dbb316aSBrad Bishop 403eb8dc403SDave Cobbley else: # fat 404eb8dc403SDave Cobbley cmd = "{} -i {} ::{}".format(self.mdel, partimg, path) 405eb8dc403SDave Cobbley try: 406eb8dc403SDave Cobbley exec_cmd(cmd) 407eb8dc403SDave Cobbley except WicError as err: 408eb8dc403SDave Cobbley if "not found" in str(err) or "non empty" in str(err): 409eb8dc403SDave Cobbley # mdel outputs 'File ... not found' or 'directory .. non empty" 410eb8dc403SDave Cobbley # try to use mdeltree as path could be a directory 411eb8dc403SDave Cobbley cmd = "{} -i {} ::{}".format(self.mdeltree, 412eb8dc403SDave Cobbley partimg, path) 413eb8dc403SDave Cobbley exec_cmd(cmd) 414eb8dc403SDave Cobbley else: 415eb8dc403SDave Cobbley raise err 416eb8dc403SDave Cobbley self._put_part_image(pnum) 417eb8dc403SDave Cobbley 418eb8dc403SDave Cobbley def write(self, target, expand): 419eb8dc403SDave Cobbley """Write disk image to the media or file.""" 420eb8dc403SDave Cobbley def write_sfdisk_script(outf, parts): 421eb8dc403SDave Cobbley for key, val in parts['partitiontable'].items(): 422eb8dc403SDave Cobbley if key in ("partitions", "device", "firstlba", "lastlba"): 423eb8dc403SDave Cobbley continue 424eb8dc403SDave Cobbley if key == "id": 425eb8dc403SDave Cobbley key = "label-id" 426eb8dc403SDave Cobbley outf.write("{}: {}\n".format(key, val)) 427eb8dc403SDave Cobbley outf.write("\n") 428eb8dc403SDave Cobbley for part in parts['partitiontable']['partitions']: 429eb8dc403SDave Cobbley line = '' 430eb8dc403SDave Cobbley for name in ('attrs', 'name', 'size', 'type', 'uuid'): 431eb8dc403SDave Cobbley if name == 'size' and part['type'] == 'f': 432eb8dc403SDave Cobbley # don't write size for extended partition 433eb8dc403SDave Cobbley continue 434eb8dc403SDave Cobbley val = part.get(name) 435eb8dc403SDave Cobbley if val: 436eb8dc403SDave Cobbley line += '{}={}, '.format(name, val) 437eb8dc403SDave Cobbley if line: 438eb8dc403SDave Cobbley line = line[:-2] # strip ', ' 439eb8dc403SDave Cobbley if part.get('bootable'): 440eb8dc403SDave Cobbley line += ' ,bootable' 441eb8dc403SDave Cobbley outf.write("{}\n".format(line)) 442eb8dc403SDave Cobbley outf.flush() 443eb8dc403SDave Cobbley 444eb8dc403SDave Cobbley def read_ptable(path): 44582c905dcSAndrew Geissler out = exec_cmd("{} -J {}".format(self.sfdisk, path)) 446eb8dc403SDave Cobbley return json.loads(out) 447eb8dc403SDave Cobbley 448eb8dc403SDave Cobbley def write_ptable(parts, target): 449eb8dc403SDave Cobbley with tempfile.NamedTemporaryFile(prefix="wic-sfdisk-", mode='w') as outf: 450eb8dc403SDave Cobbley write_sfdisk_script(outf, parts) 451d5ae7d90SBrad Bishop cmd = "{} --no-reread {} < {} ".format(self.sfdisk, target, outf.name) 452d5ae7d90SBrad Bishop exec_cmd(cmd, as_shell=True) 453eb8dc403SDave Cobbley 454eb8dc403SDave Cobbley if expand is None: 455eb8dc403SDave Cobbley sparse_copy(self.imagepath, target) 456eb8dc403SDave Cobbley else: 457eb8dc403SDave Cobbley # copy first sectors that may contain bootloader 458eb8dc403SDave Cobbley sparse_copy(self.imagepath, target, length=2048 * self._lsector_size) 459eb8dc403SDave Cobbley 460eb8dc403SDave Cobbley # copy source partition table to the target 461eb8dc403SDave Cobbley parts = read_ptable(self.imagepath) 462eb8dc403SDave Cobbley write_ptable(parts, target) 463eb8dc403SDave Cobbley 464eb8dc403SDave Cobbley # get size of unpartitioned space 465eb8dc403SDave Cobbley free = None 466eb8dc403SDave Cobbley for line in exec_cmd("{} -F {}".format(self.sfdisk, target)).splitlines(): 467eb8dc403SDave Cobbley if line.startswith("Unpartitioned space ") and line.endswith("sectors"): 468eb8dc403SDave Cobbley free = int(line.split()[-2]) 469d5ae7d90SBrad Bishop # Align free space to a 2048 sector boundary. YOCTO #12840. 470d5ae7d90SBrad Bishop free = free - (free % 2048) 471eb8dc403SDave Cobbley if free is None: 472eb8dc403SDave Cobbley raise WicError("Can't get size of unpartitioned space") 473eb8dc403SDave Cobbley 474eb8dc403SDave Cobbley # calculate expanded partitions sizes 475eb8dc403SDave Cobbley sizes = {} 476d5ae7d90SBrad Bishop num_auto_resize = 0 477eb8dc403SDave Cobbley for num, part in enumerate(parts['partitiontable']['partitions'], 1): 478eb8dc403SDave Cobbley if num in expand: 479eb8dc403SDave Cobbley if expand[num] != 0: # don't resize partition if size is set to 0 480eb8dc403SDave Cobbley sectors = expand[num] // self._lsector_size 481eb8dc403SDave Cobbley free -= sectors - part['size'] 482eb8dc403SDave Cobbley part['size'] = sectors 483eb8dc403SDave Cobbley sizes[num] = sectors 484eb8dc403SDave Cobbley elif part['type'] != 'f': 485eb8dc403SDave Cobbley sizes[num] = -1 486d5ae7d90SBrad Bishop num_auto_resize += 1 487eb8dc403SDave Cobbley 488eb8dc403SDave Cobbley for num, part in enumerate(parts['partitiontable']['partitions'], 1): 489eb8dc403SDave Cobbley if sizes.get(num) == -1: 490d5ae7d90SBrad Bishop part['size'] += free // num_auto_resize 491eb8dc403SDave Cobbley 492eb8dc403SDave Cobbley # write resized partition table to the target 493eb8dc403SDave Cobbley write_ptable(parts, target) 494eb8dc403SDave Cobbley 495eb8dc403SDave Cobbley # read resized partition table 496eb8dc403SDave Cobbley parts = read_ptable(target) 497eb8dc403SDave Cobbley 498eb8dc403SDave Cobbley # copy partitions content 499eb8dc403SDave Cobbley for num, part in enumerate(parts['partitiontable']['partitions'], 1): 500eb8dc403SDave Cobbley pnum = str(num) 501eb8dc403SDave Cobbley fstype = self.partitions[pnum].fstype 502eb8dc403SDave Cobbley 503eb8dc403SDave Cobbley # copy unchanged partition 504eb8dc403SDave Cobbley if part['size'] == self.partitions[pnum].size // self._lsector_size: 505eb8dc403SDave Cobbley logger.info("copying unchanged partition {}".format(pnum)) 506eb8dc403SDave Cobbley sparse_copy(self._get_part_image(pnum), target, seek=part['start'] * self._lsector_size) 507eb8dc403SDave Cobbley continue 508eb8dc403SDave Cobbley 509eb8dc403SDave Cobbley # resize or re-create partitions 510eb8dc403SDave Cobbley if fstype.startswith('ext') or fstype.startswith('fat') or \ 511eb8dc403SDave Cobbley fstype.startswith('linux-swap'): 512eb8dc403SDave Cobbley 513eb8dc403SDave Cobbley partfname = None 514eb8dc403SDave Cobbley with tempfile.NamedTemporaryFile(prefix="wic-part{}-".format(pnum)) as partf: 515eb8dc403SDave Cobbley partfname = partf.name 516eb8dc403SDave Cobbley 517eb8dc403SDave Cobbley if fstype.startswith('ext'): 518eb8dc403SDave Cobbley logger.info("resizing ext partition {}".format(pnum)) 519eb8dc403SDave Cobbley partimg = self._get_part_image(pnum) 520eb8dc403SDave Cobbley sparse_copy(partimg, partfname) 521eb8dc403SDave Cobbley exec_cmd("{} -pf {}".format(self.e2fsck, partfname)) 522eb8dc403SDave Cobbley exec_cmd("{} {} {}s".format(\ 523eb8dc403SDave Cobbley self.resize2fs, partfname, part['size'])) 524eb8dc403SDave Cobbley elif fstype.startswith('fat'): 525eb8dc403SDave Cobbley logger.info("copying content of the fat partition {}".format(pnum)) 526eb8dc403SDave Cobbley with tempfile.TemporaryDirectory(prefix='wic-fatdir-') as tmpdir: 527eb8dc403SDave Cobbley # copy content to the temporary directory 528eb8dc403SDave Cobbley cmd = "{} -snompi {} :: {}".format(self.mcopy, 529eb8dc403SDave Cobbley self._get_part_image(pnum), 530eb8dc403SDave Cobbley tmpdir) 531eb8dc403SDave Cobbley exec_cmd(cmd) 532eb8dc403SDave Cobbley # create new msdos partition 533eb8dc403SDave Cobbley label = part.get("name") 534eb8dc403SDave Cobbley label_str = "-n {}".format(label) if label else '' 535eb8dc403SDave Cobbley 536eb8dc403SDave Cobbley cmd = "{} {} -C {} {}".format(self.mkdosfs, label_str, partfname, 537eb8dc403SDave Cobbley part['size']) 538eb8dc403SDave Cobbley exec_cmd(cmd) 539eb8dc403SDave Cobbley # copy content from the temporary directory to the new partition 540eb8dc403SDave Cobbley cmd = "{} -snompi {} {}/* ::".format(self.mcopy, partfname, tmpdir) 541eb8dc403SDave Cobbley exec_cmd(cmd, as_shell=True) 542eb8dc403SDave Cobbley elif fstype.startswith('linux-swap'): 543eb8dc403SDave Cobbley logger.info("creating swap partition {}".format(pnum)) 544eb8dc403SDave Cobbley label = part.get("name") 545eb8dc403SDave Cobbley label_str = "-L {}".format(label) if label else '' 54682c905dcSAndrew Geissler out = exec_cmd("{} --probe {}".format(self.blkid, self._get_part_image(pnum))) 54782c905dcSAndrew Geissler uuid = out[out.index("UUID=\"")+6:out.index("UUID=\"")+42] 548eb8dc403SDave Cobbley uuid_str = "-U {}".format(uuid) if uuid else '' 549eb8dc403SDave Cobbley with open(partfname, 'w') as sparse: 550eb8dc403SDave Cobbley os.ftruncate(sparse.fileno(), part['size'] * self._lsector_size) 551eb8dc403SDave Cobbley exec_cmd("{} {} {} {}".format(self.mkswap, label_str, uuid_str, partfname)) 552eb8dc403SDave Cobbley sparse_copy(partfname, target, seek=part['start'] * self._lsector_size) 553eb8dc403SDave Cobbley os.unlink(partfname) 554eb8dc403SDave Cobbley elif part['type'] != 'f': 5551a4b7ee2SBrad Bishop logger.warning("skipping partition {}: unsupported fstype {}".format(pnum, fstype)) 556eb8dc403SDave Cobbley 557eb8dc403SDave Cobbleydef wic_ls(args, native_sysroot): 558eb8dc403SDave Cobbley """List contents of partitioned image or vfat partition.""" 559eb8dc403SDave Cobbley disk = Disk(args.path.image, native_sysroot) 560eb8dc403SDave Cobbley if not args.path.part: 561eb8dc403SDave Cobbley if disk.partitions: 562eb8dc403SDave Cobbley print('Num Start End Size Fstype') 563eb8dc403SDave Cobbley for part in disk.partitions.values(): 564eb8dc403SDave Cobbley print("{:2d} {:12d} {:12d} {:12d} {}".format(\ 565eb8dc403SDave Cobbley part.pnum, part.start, part.end, 566eb8dc403SDave Cobbley part.size, part.fstype)) 567eb8dc403SDave Cobbley else: 568eb8dc403SDave Cobbley path = args.path.path or '/' 569eb8dc403SDave Cobbley print(disk.dir(args.path.part, path)) 570eb8dc403SDave Cobbley 571eb8dc403SDave Cobbleydef wic_cp(args, native_sysroot): 572eb8dc403SDave Cobbley """ 57382c905dcSAndrew Geissler Copy file or directory to/from the vfat/ext partition of 574eb8dc403SDave Cobbley partitioned image. 575eb8dc403SDave Cobbley """ 57682c905dcSAndrew Geissler if isinstance(args.dest, str): 57782c905dcSAndrew Geissler disk = Disk(args.src.image, native_sysroot) 57882c905dcSAndrew Geissler else: 579eb8dc403SDave Cobbley disk = Disk(args.dest.image, native_sysroot) 58082c905dcSAndrew Geissler disk.copy(args.src, args.dest) 58182c905dcSAndrew Geissler 582eb8dc403SDave Cobbley 583eb8dc403SDave Cobbleydef wic_rm(args, native_sysroot): 584eb8dc403SDave Cobbley """ 585eb8dc403SDave Cobbley Remove files or directories from the vfat partition of 586eb8dc403SDave Cobbley partitioned image. 587eb8dc403SDave Cobbley """ 588eb8dc403SDave Cobbley disk = Disk(args.path.image, native_sysroot) 5896dbb316aSBrad Bishop disk.remove(args.path.part, args.path.path, args.recursive_delete) 590eb8dc403SDave Cobbley 591eb8dc403SDave Cobbleydef wic_write(args, native_sysroot): 592eb8dc403SDave Cobbley """ 593eb8dc403SDave Cobbley Write image to a target device. 594eb8dc403SDave Cobbley """ 59564c979e8SBrad Bishop disk = Disk(args.image, native_sysroot, ('fat', 'ext', 'linux-swap')) 596eb8dc403SDave Cobbley disk.write(args.target, args.expand) 597eb8dc403SDave Cobbley 598eb8dc403SDave Cobbleydef find_canned(scripts_path, file_name): 599eb8dc403SDave Cobbley """ 600eb8dc403SDave Cobbley Find a file either by its path or by name in the canned files dir. 601eb8dc403SDave Cobbley 602eb8dc403SDave Cobbley Return None if not found 603eb8dc403SDave Cobbley """ 604eb8dc403SDave Cobbley if os.path.exists(file_name): 605eb8dc403SDave Cobbley return file_name 606eb8dc403SDave Cobbley 607eb8dc403SDave Cobbley layers_canned_wks_dir = build_canned_image_list(scripts_path) 608eb8dc403SDave Cobbley for canned_wks_dir in layers_canned_wks_dir: 609eb8dc403SDave Cobbley for root, dirs, files in os.walk(canned_wks_dir): 610eb8dc403SDave Cobbley for fname in files: 611eb8dc403SDave Cobbley if fname == file_name: 612eb8dc403SDave Cobbley fullpath = os.path.join(canned_wks_dir, fname) 613eb8dc403SDave Cobbley return fullpath 614eb8dc403SDave Cobbley 615eb8dc403SDave Cobbleydef get_custom_config(boot_file): 616eb8dc403SDave Cobbley """ 617eb8dc403SDave Cobbley Get the custom configuration to be used for the bootloader. 618eb8dc403SDave Cobbley 619eb8dc403SDave Cobbley Return None if the file can't be found. 620eb8dc403SDave Cobbley """ 621eb8dc403SDave Cobbley # Get the scripts path of poky 622eb8dc403SDave Cobbley scripts_path = os.path.abspath("%s/../.." % os.path.dirname(__file__)) 623eb8dc403SDave Cobbley 624eb8dc403SDave Cobbley cfg_file = find_canned(scripts_path, boot_file) 625eb8dc403SDave Cobbley if cfg_file: 626eb8dc403SDave Cobbley with open(cfg_file, "r") as f: 627eb8dc403SDave Cobbley config = f.read() 628eb8dc403SDave Cobbley return config 629