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