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