xref: /openbmc/openbmc/poky/scripts/lib/wic/engine.py (revision 595f6308)
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