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
57d1e89497SAndrew 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
62d1e89497SAndrew 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"))
66d1e89497SAndrew 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,
805199d831SAndrew Geissler                                       self.parts, self.native_sysroot,
815199d831SAndrew Geissler                                       options.extra_space)
82eb8dc403SDave Cobbley
83d1e89497SAndrew Geissler    def setup_workdir(self, workdir):
84d1e89497SAndrew Geissler        if workdir:
85d1e89497SAndrew Geissler            if os.path.exists(workdir):
86d1e89497SAndrew Geissler                raise WicError("Internal workdir '%s' specified in wic arguments already exists!" % (workdir))
87d1e89497SAndrew Geissler
88d1e89497SAndrew Geissler            os.makedirs(workdir)
89d1e89497SAndrew Geissler            return workdir
90d1e89497SAndrew Geissler        else:
91d1e89497SAndrew Geissler            return tempfile.mkdtemp(dir=self.outdir, prefix='tmp.wic.')
92d1e89497SAndrew Geissler
93eb8dc403SDave Cobbley    def do_create(self):
94eb8dc403SDave Cobbley        """
95eb8dc403SDave Cobbley        Plugin entry point.
96eb8dc403SDave Cobbley        """
97eb8dc403SDave Cobbley        try:
98eb8dc403SDave Cobbley            self.create()
99eb8dc403SDave Cobbley            self.assemble()
100eb8dc403SDave Cobbley            self.finalize()
101eb8dc403SDave Cobbley            self.print_info()
102eb8dc403SDave Cobbley        finally:
103eb8dc403SDave Cobbley            self.cleanup()
104eb8dc403SDave Cobbley
105d1e89497SAndrew Geissler    def update_fstab(self, image_rootfs):
106d1e89497SAndrew Geissler        """Assume partition order same as in wks"""
107eb8dc403SDave Cobbley        if not image_rootfs:
108eb8dc403SDave Cobbley            return
109eb8dc403SDave Cobbley
110eb8dc403SDave Cobbley        fstab_path = image_rootfs + "/etc/fstab"
111eb8dc403SDave Cobbley        if not os.path.isfile(fstab_path):
112eb8dc403SDave Cobbley            return
113eb8dc403SDave Cobbley
114eb8dc403SDave Cobbley        with open(fstab_path) as fstab:
115eb8dc403SDave Cobbley            fstab_lines = fstab.readlines()
116eb8dc403SDave Cobbley
117eb8dc403SDave Cobbley        updated = False
118d1e89497SAndrew Geissler        for part in self.parts:
119eb8dc403SDave Cobbley            if not part.realnum or not part.mountpoint \
1202390b1b6SPatrick Williams               or part.mountpoint == "/" or not (part.mountpoint.startswith('/') or part.mountpoint == "swap"):
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"
141d583833aSAndrew Geissler            passno = part.fspassno if part.fspassno else "0"
142eb8dc403SDave Cobbley            line = "\t".join([device_name, part.mountpoint, part.fstype,
143d583833aSAndrew Geissler                              opts, "0", passno]) + "\n"
144eb8dc403SDave Cobbley
145eb8dc403SDave Cobbley            fstab_lines.append(line)
146eb8dc403SDave Cobbley            updated = True
147eb8dc403SDave Cobbley
148d1e89497SAndrew Geissler        if updated:
149d1e89497SAndrew Geissler            self.updated_fstab_path = os.path.join(self.workdir, "fstab")
150d1e89497SAndrew Geissler            with open(self.updated_fstab_path, "w") as f:
151d1e89497SAndrew Geissler                f.writelines(fstab_lines)
1522390b1b6SPatrick Williams            if os.getenv('SOURCE_DATE_EPOCH'):
1532390b1b6SPatrick Williams                fstab_time = int(os.getenv('SOURCE_DATE_EPOCH'))
1542390b1b6SPatrick Williams                os.utime(self.updated_fstab_path, (fstab_time, fstab_time))
155eb8dc403SDave Cobbley
156eb8dc403SDave Cobbley    def _full_path(self, path, name, extention):
157eb8dc403SDave Cobbley        """ Construct full file path to a file we generate. """
158eb8dc403SDave Cobbley        return os.path.join(path, "%s-%s.%s" % (self.name, name, extention))
159eb8dc403SDave Cobbley
160eb8dc403SDave Cobbley    #
161eb8dc403SDave Cobbley    # Actual implemention
162eb8dc403SDave Cobbley    #
163eb8dc403SDave Cobbley    def create(self):
164eb8dc403SDave Cobbley        """
165eb8dc403SDave Cobbley        For 'wic', we already have our build artifacts - we just create
166eb8dc403SDave Cobbley        filesystems from the artifacts directly and combine them into
167eb8dc403SDave Cobbley        a partitioned image.
168eb8dc403SDave Cobbley        """
16996ff1984SBrad Bishop        if not self.no_fstab_update:
170d1e89497SAndrew Geissler            self.update_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
171eb8dc403SDave Cobbley
172eb8dc403SDave Cobbley        for part in self.parts:
173eb8dc403SDave Cobbley            # get rootfs size from bitbake variable if it's not set in .ks file
174eb8dc403SDave Cobbley            if not part.size:
175eb8dc403SDave Cobbley                # and if rootfs name is specified for the partition
176eb8dc403SDave Cobbley                image_name = self.rootfs_dir.get(part.rootfs_dir)
177eb8dc403SDave Cobbley                if image_name and os.path.sep not in image_name:
178eb8dc403SDave Cobbley                    # Bitbake variable ROOTFS_SIZE is calculated in
179eb8dc403SDave Cobbley                    # Image._get_rootfs_size method from meta/lib/oe/image.py
180eb8dc403SDave Cobbley                    # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
181eb8dc403SDave Cobbley                    # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
182eb8dc403SDave Cobbley                    rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
183eb8dc403SDave Cobbley                    if rsize_bb:
184eb8dc403SDave Cobbley                        part.size = int(round(float(rsize_bb)))
185eb8dc403SDave Cobbley
186eb8dc403SDave Cobbley        self._image.prepare(self)
187eb8dc403SDave Cobbley        self._image.layout_partitions()
188eb8dc403SDave Cobbley        self._image.create()
189eb8dc403SDave Cobbley
190eb8dc403SDave Cobbley    def assemble(self):
191eb8dc403SDave Cobbley        """
192eb8dc403SDave Cobbley        Assemble partitions into disk image
193eb8dc403SDave Cobbley        """
194eb8dc403SDave Cobbley        self._image.assemble()
195eb8dc403SDave Cobbley
196eb8dc403SDave Cobbley    def finalize(self):
197eb8dc403SDave Cobbley        """
198eb8dc403SDave Cobbley        Finalize the disk image.
199eb8dc403SDave Cobbley
200eb8dc403SDave Cobbley        For example, prepare the image to be bootable by e.g.
201eb8dc403SDave Cobbley        creating and installing a bootloader configuration.
202eb8dc403SDave Cobbley        """
203eb8dc403SDave Cobbley        source_plugin = self.ks.bootloader.source
204eb8dc403SDave Cobbley        disk_name = self.parts[0].disk
205eb8dc403SDave Cobbley        if source_plugin:
206eb8dc403SDave Cobbley            plugin = PluginMgr.get_plugins('source')[source_plugin]
207eb8dc403SDave Cobbley            plugin.do_install_disk(self._image, disk_name, self, self.workdir,
208eb8dc403SDave Cobbley                                   self.oe_builddir, self.bootimg_dir,
209eb8dc403SDave Cobbley                                   self.kernel_dir, self.native_sysroot)
210eb8dc403SDave Cobbley
211eb8dc403SDave Cobbley        full_path = self._image.path
212eb8dc403SDave Cobbley        # Generate .bmap
213eb8dc403SDave Cobbley        if self.bmap:
214eb8dc403SDave Cobbley            logger.debug("Generating bmap file for %s", disk_name)
215eb8dc403SDave Cobbley            python = os.path.join(self.native_sysroot, 'usr/bin/python3-native/python3')
216eb8dc403SDave Cobbley            bmaptool = os.path.join(self.native_sysroot, 'usr/bin/bmaptool')
217eb8dc403SDave Cobbley            exec_native_cmd("%s %s create %s -o %s.bmap" % \
218eb8dc403SDave Cobbley                            (python, bmaptool, full_path, full_path), self.native_sysroot)
219eb8dc403SDave Cobbley        # Compress the image
220eb8dc403SDave Cobbley        if self.compressor:
221eb8dc403SDave Cobbley            logger.debug("Compressing disk %s with %s", disk_name, self.compressor)
222eb8dc403SDave Cobbley            exec_cmd("%s %s" % (self.compressor, full_path))
223eb8dc403SDave Cobbley
224eb8dc403SDave Cobbley    def print_info(self):
225eb8dc403SDave Cobbley        """
226eb8dc403SDave Cobbley        Print the image(s) and artifacts used, for the user.
227eb8dc403SDave Cobbley        """
228eb8dc403SDave Cobbley        msg = "The new image(s) can be found here:\n"
229eb8dc403SDave Cobbley
230eb8dc403SDave Cobbley        extension = "direct" + {"gzip": ".gz",
231eb8dc403SDave Cobbley                                "bzip2": ".bz2",
232eb8dc403SDave Cobbley                                "xz": ".xz",
233eb8dc403SDave Cobbley                                None: ""}.get(self.compressor)
234eb8dc403SDave Cobbley        full_path = self._full_path(self.outdir, self.parts[0].disk, extension)
235eb8dc403SDave Cobbley        msg += '  %s\n\n' % full_path
236eb8dc403SDave Cobbley
237eb8dc403SDave Cobbley        msg += 'The following build artifacts were used to create the image(s):\n'
238eb8dc403SDave Cobbley        for part in self.parts:
239eb8dc403SDave Cobbley            if part.rootfs_dir is None:
240eb8dc403SDave Cobbley                continue
241eb8dc403SDave Cobbley            if part.mountpoint == '/':
242eb8dc403SDave Cobbley                suffix = ':'
243eb8dc403SDave Cobbley            else:
244eb8dc403SDave Cobbley                suffix = '["%s"]:' % (part.mountpoint or part.label)
245eb8dc403SDave Cobbley            rootdir = part.rootfs_dir
246eb8dc403SDave Cobbley            msg += '  ROOTFS_DIR%s%s\n' % (suffix.ljust(20), rootdir)
247eb8dc403SDave Cobbley
248eb8dc403SDave Cobbley        msg += '  BOOTIMG_DIR:                  %s\n' % self.bootimg_dir
249eb8dc403SDave Cobbley        msg += '  KERNEL_DIR:                   %s\n' % self.kernel_dir
250eb8dc403SDave Cobbley        msg += '  NATIVE_SYSROOT:               %s\n' % self.native_sysroot
251eb8dc403SDave Cobbley
252eb8dc403SDave Cobbley        logger.info(msg)
253eb8dc403SDave Cobbley
254eb8dc403SDave Cobbley    @property
255eb8dc403SDave Cobbley    def rootdev(self):
256eb8dc403SDave Cobbley        """
257eb8dc403SDave Cobbley        Get root device name to use as a 'root' parameter
258eb8dc403SDave Cobbley        in kernel command line.
259eb8dc403SDave Cobbley
260eb8dc403SDave Cobbley        Assume partition order same as in wks
261eb8dc403SDave Cobbley        """
262eb8dc403SDave Cobbley        for part in self.parts:
263eb8dc403SDave Cobbley            if part.mountpoint == "/":
264eb8dc403SDave Cobbley                if part.uuid:
265eb8dc403SDave Cobbley                    return "PARTUUID=%s" % part.uuid
26603907ee1SPatrick Williams                elif part.label and self.ptable_format != 'msdos':
267595f6308SAndrew Geissler                    return "PARTLABEL=%s" % part.label
268eb8dc403SDave Cobbley                else:
269eb8dc403SDave Cobbley                    suffix = 'p' if part.disk.startswith('mmcblk') else ''
270eb8dc403SDave Cobbley                    return "/dev/%s%s%-d" % (part.disk, suffix, part.realnum)
271eb8dc403SDave Cobbley
272eb8dc403SDave Cobbley    def cleanup(self):
273eb8dc403SDave Cobbley        if self._image:
274eb8dc403SDave Cobbley            self._image.cleanup()
275eb8dc403SDave Cobbley
276eb8dc403SDave Cobbley        # Move results to the output dir
277eb8dc403SDave Cobbley        if not os.path.exists(self.outdir):
278eb8dc403SDave Cobbley            os.makedirs(self.outdir)
279eb8dc403SDave Cobbley
280eb8dc403SDave Cobbley        for fname in os.listdir(self.workdir):
281eb8dc403SDave Cobbley            path = os.path.join(self.workdir, fname)
282eb8dc403SDave Cobbley            if os.path.isfile(path):
283eb8dc403SDave Cobbley                shutil.move(path, os.path.join(self.outdir, fname))
284eb8dc403SDave Cobbley
285d1e89497SAndrew Geissler        # remove work directory when it is not in debugging mode
286d1e89497SAndrew Geissler        if not self.debug:
287eb8dc403SDave Cobbley            shutil.rmtree(self.workdir, ignore_errors=True)
288eb8dc403SDave Cobbley
289eb8dc403SDave Cobbley# Overhead of the MBR partitioning scheme (just one sector)
290eb8dc403SDave CobbleyMBR_OVERHEAD = 1
291eb8dc403SDave Cobbley
292eb8dc403SDave Cobbley# Overhead of the GPT partitioning scheme
293eb8dc403SDave CobbleyGPT_OVERHEAD = 34
294eb8dc403SDave Cobbley
295eb8dc403SDave Cobbley# Size of a sector in bytes
296eb8dc403SDave CobbleySECTOR_SIZE = 512
297eb8dc403SDave Cobbley
298eb8dc403SDave Cobbleyclass PartitionedImage():
299eb8dc403SDave Cobbley    """
300eb8dc403SDave Cobbley    Partitioned image in a file.
301eb8dc403SDave Cobbley    """
302eb8dc403SDave Cobbley
3035199d831SAndrew Geissler    def __init__(self, path, ptable_format, partitions, native_sysroot=None, extra_space=0):
304eb8dc403SDave Cobbley        self.path = path  # Path to the image file
305eb8dc403SDave Cobbley        self.numpart = 0  # Number of allocated partitions
306eb8dc403SDave Cobbley        self.realpart = 0 # Number of partitions in the partition table
30708902b01SBrad Bishop        self.primary_part_num = 0  # Number of primary partitions (msdos)
30808902b01SBrad Bishop        self.extendedpart = 0      # Create extended partition before this logical partition (msdos)
30908902b01SBrad Bishop        self.extended_size_sec = 0 # Size of exteded partition (msdos)
31008902b01SBrad Bishop        self.logical_part_cnt = 0  # Number of total logical paritions (msdos)
311eb8dc403SDave Cobbley        self.offset = 0   # Offset of next partition (in sectors)
312eb8dc403SDave Cobbley        self.min_size = 0 # Minimum required disk size to fit
313eb8dc403SDave Cobbley                          # all partitions (in bytes)
314eb8dc403SDave Cobbley        self.ptable_format = ptable_format  # Partition table format
315eb8dc403SDave Cobbley        # Disk system identifier
3162390b1b6SPatrick Williams        if os.getenv('SOURCE_DATE_EPOCH'):
3172390b1b6SPatrick Williams            self.identifier = random.Random(int(os.getenv('SOURCE_DATE_EPOCH'))).randint(1, 0xffffffff)
3182390b1b6SPatrick Williams        else:
319eb8dc403SDave Cobbley            self.identifier = random.SystemRandom().randint(1, 0xffffffff)
320eb8dc403SDave Cobbley
321eb8dc403SDave Cobbley        self.partitions = partitions
322eb8dc403SDave Cobbley        self.partimages = []
323eb8dc403SDave Cobbley        # Size of a sector used in calculations
324eb8dc403SDave Cobbley        self.sector_size = SECTOR_SIZE
325eb8dc403SDave Cobbley        self.native_sysroot = native_sysroot
326f3f93bb8SBrad Bishop        num_real_partitions = len([p for p in self.partitions if not p.no_table])
3275199d831SAndrew Geissler        self.extra_space = extra_space
328eb8dc403SDave Cobbley
329eb8dc403SDave Cobbley        # calculate the real partition number, accounting for partitions not
330eb8dc403SDave Cobbley        # in the partition table and logical partitions
331eb8dc403SDave Cobbley        realnum = 0
332eb8dc403SDave Cobbley        for part in self.partitions:
333eb8dc403SDave Cobbley            if part.no_table:
334eb8dc403SDave Cobbley                part.realnum = 0
335eb8dc403SDave Cobbley            else:
336eb8dc403SDave Cobbley                realnum += 1
337f3f93bb8SBrad Bishop                if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4:
338eb8dc403SDave Cobbley                    part.realnum = realnum + 1
339eb8dc403SDave Cobbley                    continue
340eb8dc403SDave Cobbley                part.realnum = realnum
341eb8dc403SDave Cobbley
342eb8dc403SDave Cobbley        # generate parition and filesystem UUIDs
343eb8dc403SDave Cobbley        for part in self.partitions:
344eb8dc403SDave Cobbley            if not part.uuid and part.use_uuid:
345eb8dc403SDave Cobbley                if self.ptable_format == 'gpt':
346eb8dc403SDave Cobbley                    part.uuid = str(uuid.uuid4())
347eb8dc403SDave Cobbley                else: # msdos partition table
348eb8dc403SDave Cobbley                    part.uuid = '%08x-%02d' % (self.identifier, part.realnum)
349eb8dc403SDave Cobbley            if not part.fsuuid:
350eb8dc403SDave Cobbley                if part.fstype == 'vfat' or part.fstype == 'msdos':
351eb8dc403SDave Cobbley                    part.fsuuid = '0x' + str(uuid.uuid4())[:8].upper()
352eb8dc403SDave Cobbley                else:
353eb8dc403SDave Cobbley                    part.fsuuid = str(uuid.uuid4())
354d1e89497SAndrew Geissler            else:
355d1e89497SAndrew Geissler                #make sure the fsuuid for vfat/msdos align with format 0xYYYYYYYY
356d1e89497SAndrew Geissler                if part.fstype == 'vfat' or part.fstype == 'msdos':
357d1e89497SAndrew Geissler                    if part.fsuuid.upper().startswith("0X"):
358d1e89497SAndrew Geissler                        part.fsuuid = '0x' + part.fsuuid.upper()[2:].rjust(8,"0")
359d1e89497SAndrew Geissler                    else:
360d1e89497SAndrew Geissler                        part.fsuuid = '0x' + part.fsuuid.upper().rjust(8,"0")
361eb8dc403SDave Cobbley
362eb8dc403SDave Cobbley    def prepare(self, imager):
363eb8dc403SDave Cobbley        """Prepare an image. Call prepare method of all image partitions."""
364eb8dc403SDave Cobbley        for part in self.partitions:
365eb8dc403SDave Cobbley            # need to create the filesystems in order to get their
366eb8dc403SDave Cobbley            # sizes before we can add them and do the layout.
367eb8dc403SDave Cobbley            part.prepare(imager, imager.workdir, imager.oe_builddir,
368eb8dc403SDave Cobbley                         imager.rootfs_dir, imager.bootimg_dir,
369d1e89497SAndrew Geissler                         imager.kernel_dir, imager.native_sysroot,
370d1e89497SAndrew Geissler                         imager.updated_fstab_path)
371eb8dc403SDave Cobbley
372eb8dc403SDave Cobbley            # Converting kB to sectors for parted
373eb8dc403SDave Cobbley            part.size_sec = part.disk_size * 1024 // self.sector_size
374eb8dc403SDave Cobbley
375eb8dc403SDave Cobbley    def layout_partitions(self):
376eb8dc403SDave Cobbley        """ Layout the partitions, meaning calculate the position of every
377eb8dc403SDave Cobbley        partition on the disk. The 'ptable_format' parameter defines the
378eb8dc403SDave Cobbley        partition table format and may be "msdos". """
379eb8dc403SDave Cobbley
380eb8dc403SDave Cobbley        logger.debug("Assigning %s partitions to disks", self.ptable_format)
381eb8dc403SDave Cobbley
382eb8dc403SDave Cobbley        # The number of primary and logical partitions. Extended partition and
383eb8dc403SDave Cobbley        # partitions not listed in the table are not included.
384eb8dc403SDave Cobbley        num_real_partitions = len([p for p in self.partitions if not p.no_table])
385eb8dc403SDave Cobbley
386eb8dc403SDave Cobbley        # Go through partitions in the order they are added in .ks file
387eb8dc403SDave Cobbley        for num in range(len(self.partitions)):
388eb8dc403SDave Cobbley            part = self.partitions[num]
389eb8dc403SDave Cobbley
390eb8dc403SDave Cobbley            if self.ptable_format == 'msdos' and part.part_name:
391eb8dc403SDave Cobbley                raise WicError("setting custom partition name is not " \
392eb8dc403SDave Cobbley                               "implemented for msdos partitions")
393eb8dc403SDave Cobbley
394eb8dc403SDave Cobbley            if self.ptable_format == 'msdos' and part.part_type:
395eb8dc403SDave Cobbley                # The --part-type can also be implemented for MBR partitions,
396eb8dc403SDave Cobbley                # in which case it would map to the 1-byte "partition type"
397eb8dc403SDave Cobbley                # filed at offset 3 of the partition entry.
398eb8dc403SDave Cobbley                raise WicError("setting custom partition type is not " \
399eb8dc403SDave Cobbley                               "implemented for msdos partitions")
400eb8dc403SDave Cobbley
401eb8dc403SDave Cobbley            # Get the disk where the partition is located
402eb8dc403SDave Cobbley            self.numpart += 1
403eb8dc403SDave Cobbley            if not part.no_table:
404eb8dc403SDave Cobbley                self.realpart += 1
405eb8dc403SDave Cobbley
406eb8dc403SDave Cobbley            if self.numpart == 1:
407eb8dc403SDave Cobbley                if self.ptable_format == "msdos":
408eb8dc403SDave Cobbley                    overhead = MBR_OVERHEAD
409eb8dc403SDave Cobbley                elif self.ptable_format == "gpt":
410eb8dc403SDave Cobbley                    overhead = GPT_OVERHEAD
411eb8dc403SDave Cobbley
412eb8dc403SDave Cobbley                # Skip one sector required for the partitioning scheme overhead
413eb8dc403SDave Cobbley                self.offset += overhead
414eb8dc403SDave Cobbley
41508902b01SBrad Bishop            if self.ptable_format == "msdos":
41608902b01SBrad Bishop                if self.primary_part_num > 3 or \
41708902b01SBrad Bishop                   (self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4):
41808902b01SBrad Bishop                    part.type = 'logical'
419eb8dc403SDave Cobbley                # Reserve a sector for EBR for every logical partition
420eb8dc403SDave Cobbley                # before alignment is performed.
42108902b01SBrad Bishop                if part.type == 'logical':
42282c905dcSAndrew Geissler                    self.offset += 2
423eb8dc403SDave Cobbley
42408902b01SBrad Bishop            align_sectors = 0
425eb8dc403SDave Cobbley            if part.align:
426eb8dc403SDave Cobbley                # If not first partition and we do have alignment set we need
427eb8dc403SDave Cobbley                # to align the partition.
428eb8dc403SDave Cobbley                # FIXME: This leaves a empty spaces to the disk. To fill the
429eb8dc403SDave Cobbley                # gaps we could enlargea the previous partition?
430eb8dc403SDave Cobbley
431eb8dc403SDave Cobbley                # Calc how much the alignment is off.
432eb8dc403SDave Cobbley                align_sectors = self.offset % (part.align * 1024 // self.sector_size)
433eb8dc403SDave Cobbley
434eb8dc403SDave Cobbley                if align_sectors:
435eb8dc403SDave Cobbley                    # If partition is not aligned as required, we need
436eb8dc403SDave Cobbley                    # to move forward to the next alignment point
437eb8dc403SDave Cobbley                    align_sectors = (part.align * 1024 // self.sector_size) - align_sectors
438eb8dc403SDave Cobbley
439eb8dc403SDave Cobbley                    logger.debug("Realignment for %s%s with %s sectors, original"
440eb8dc403SDave Cobbley                                 " offset %s, target alignment is %sK.",
441eb8dc403SDave Cobbley                                 part.disk, self.numpart, align_sectors,
442eb8dc403SDave Cobbley                                 self.offset, part.align)
443eb8dc403SDave Cobbley
444eb8dc403SDave Cobbley                    # increase the offset so we actually start the partition on right alignment
445eb8dc403SDave Cobbley                    self.offset += align_sectors
446eb8dc403SDave Cobbley
4474ed12e16SAndrew Geissler            if part.offset is not None:
448c9f7865aSAndrew Geissler                offset = part.offset // self.sector_size
4494ed12e16SAndrew Geissler
450c9f7865aSAndrew Geissler                if offset * self.sector_size != part.offset:
451c9f7865aSAndrew Geissler                    raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size))
4524ed12e16SAndrew Geissler
4534ed12e16SAndrew Geissler                delta = offset - self.offset
4544ed12e16SAndrew Geissler                if delta < 0:
455c9f7865aSAndrew 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))
4564ed12e16SAndrew Geissler
4574ed12e16SAndrew Geissler                logger.debug("Skipping %d sectors to place %s%s at offset %dK",
4584ed12e16SAndrew Geissler                             delta, part.disk, self.numpart, part.offset)
4594ed12e16SAndrew Geissler
4604ed12e16SAndrew Geissler                self.offset = offset
4614ed12e16SAndrew Geissler
462eb8dc403SDave Cobbley            part.start = self.offset
463eb8dc403SDave Cobbley            self.offset += part.size_sec
464eb8dc403SDave Cobbley
465eb8dc403SDave Cobbley            if not part.no_table:
466eb8dc403SDave Cobbley                part.num = self.realpart
467eb8dc403SDave Cobbley            else:
468eb8dc403SDave Cobbley                part.num = 0
469eb8dc403SDave Cobbley
47008902b01SBrad Bishop            if self.ptable_format == "msdos" and not part.no_table:
47108902b01SBrad Bishop                if part.type == 'logical':
47208902b01SBrad Bishop                    self.logical_part_cnt += 1
47308902b01SBrad Bishop                    part.num = self.logical_part_cnt + 4
47408902b01SBrad Bishop                    if self.extendedpart == 0:
47508902b01SBrad Bishop                        # Create extended partition as a primary partition
47608902b01SBrad Bishop                        self.primary_part_num += 1
47708902b01SBrad Bishop                        self.extendedpart = part.num
47808902b01SBrad Bishop                    else:
47908902b01SBrad Bishop                        self.extended_size_sec += align_sectors
48082c905dcSAndrew Geissler                    self.extended_size_sec += part.size_sec + 2
48108902b01SBrad Bishop                else:
48208902b01SBrad Bishop                    self.primary_part_num += 1
48308902b01SBrad Bishop                    part.num = self.primary_part_num
484eb8dc403SDave Cobbley
485eb8dc403SDave Cobbley            logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
486eb8dc403SDave Cobbley                         "sectors (%d bytes).", part.mountpoint, part.disk,
487eb8dc403SDave Cobbley                         part.num, part.start, self.offset - 1, part.size_sec,
488eb8dc403SDave Cobbley                         part.size_sec * self.sector_size)
489eb8dc403SDave Cobbley
490eb8dc403SDave Cobbley        # Once all the partitions have been layed out, we can calculate the
491eb8dc403SDave Cobbley        # minumim disk size
492eb8dc403SDave Cobbley        self.min_size = self.offset
493eb8dc403SDave Cobbley        if self.ptable_format == "gpt":
494eb8dc403SDave Cobbley            self.min_size += GPT_OVERHEAD
495eb8dc403SDave Cobbley
496eb8dc403SDave Cobbley        self.min_size *= self.sector_size
4975199d831SAndrew Geissler        self.min_size += self.extra_space
498eb8dc403SDave Cobbley
499eb8dc403SDave Cobbley    def _create_partition(self, device, parttype, fstype, start, size):
500eb8dc403SDave Cobbley        """ Create a partition on an image described by the 'device' object. """
501eb8dc403SDave Cobbley
502eb8dc403SDave Cobbley        # Start is included to the size so we need to substract one from the end.
503eb8dc403SDave Cobbley        end = start + size - 1
504eb8dc403SDave Cobbley        logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors",
505eb8dc403SDave Cobbley                     parttype, start, end, size)
506eb8dc403SDave Cobbley
507eb8dc403SDave Cobbley        cmd = "parted -s %s unit s mkpart %s" % (device, parttype)
508eb8dc403SDave Cobbley        if fstype:
509eb8dc403SDave Cobbley            cmd += " %s" % fstype
510eb8dc403SDave Cobbley        cmd += " %d %d" % (start, end)
511eb8dc403SDave Cobbley
512eb8dc403SDave Cobbley        return exec_native_cmd(cmd, self.native_sysroot)
513eb8dc403SDave Cobbley
514eb8dc403SDave Cobbley    def create(self):
515eb8dc403SDave Cobbley        logger.debug("Creating sparse file %s", self.path)
516eb8dc403SDave Cobbley        with open(self.path, 'w') as sparse:
517eb8dc403SDave Cobbley            os.ftruncate(sparse.fileno(), self.min_size)
518eb8dc403SDave Cobbley
519eb8dc403SDave Cobbley        logger.debug("Initializing partition table for %s", self.path)
520eb8dc403SDave Cobbley        exec_native_cmd("parted -s %s mklabel %s" %
521eb8dc403SDave Cobbley                        (self.path, self.ptable_format), self.native_sysroot)
522eb8dc403SDave Cobbley
523eb8dc403SDave Cobbley        logger.debug("Set disk identifier %x", self.identifier)
524eb8dc403SDave Cobbley        with open(self.path, 'r+b') as img:
525eb8dc403SDave Cobbley            img.seek(0x1B8)
526eb8dc403SDave Cobbley            img.write(self.identifier.to_bytes(4, 'little'))
527eb8dc403SDave Cobbley
528eb8dc403SDave Cobbley        logger.debug("Creating partitions")
529eb8dc403SDave Cobbley
530eb8dc403SDave Cobbley        for part in self.partitions:
531eb8dc403SDave Cobbley            if part.num == 0:
532eb8dc403SDave Cobbley                continue
533eb8dc403SDave Cobbley
53408902b01SBrad Bishop            if self.ptable_format == "msdos" and part.num == self.extendedpart:
535eb8dc403SDave Cobbley                # Create an extended partition (note: extended
536eb8dc403SDave Cobbley                # partition is described in MBR and contains all
537eb8dc403SDave Cobbley                # logical partitions). The logical partitions save a
538eb8dc403SDave Cobbley                # sector for an EBR just before the start of a
539eb8dc403SDave Cobbley                # partition. The extended partition must start one
540eb8dc403SDave Cobbley                # sector before the start of the first logical
541eb8dc403SDave Cobbley                # partition. This way the first EBR is inside of the
542eb8dc403SDave Cobbley                # extended partition. Since the extended partitions
543eb8dc403SDave Cobbley                # starts a sector before the first logical partition,
544eb8dc403SDave Cobbley                # add a sector at the back, so that there is enough
545eb8dc403SDave Cobbley                # room for all logical partitions.
546eb8dc403SDave Cobbley                self._create_partition(self.path, "extended",
54782c905dcSAndrew Geissler                                       None, part.start - 2,
54808902b01SBrad Bishop                                       self.extended_size_sec)
549eb8dc403SDave Cobbley
550eb8dc403SDave Cobbley            if part.fstype == "swap":
551eb8dc403SDave Cobbley                parted_fs_type = "linux-swap"
552eb8dc403SDave Cobbley            elif part.fstype == "vfat":
553eb8dc403SDave Cobbley                parted_fs_type = "fat32"
554eb8dc403SDave Cobbley            elif part.fstype == "msdos":
555eb8dc403SDave Cobbley                parted_fs_type = "fat16"
556eb8dc403SDave Cobbley                if not part.system_id:
557eb8dc403SDave Cobbley                    part.system_id = '0x6' # FAT16
558eb8dc403SDave Cobbley            else:
559eb8dc403SDave Cobbley                # Type for ext2/ext3/ext4/btrfs
560eb8dc403SDave Cobbley                parted_fs_type = "ext2"
561eb8dc403SDave Cobbley
562eb8dc403SDave Cobbley            # Boot ROM of OMAP boards require vfat boot partition to have an
563eb8dc403SDave Cobbley            # even number of sectors.
564eb8dc403SDave Cobbley            if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \
565eb8dc403SDave Cobbley               and part.size_sec % 2:
566eb8dc403SDave Cobbley                logger.debug("Subtracting one sector from '%s' partition to "
567eb8dc403SDave Cobbley                             "get even number of sectors for the partition",
568eb8dc403SDave Cobbley                             part.mountpoint)
569eb8dc403SDave Cobbley                part.size_sec -= 1
570eb8dc403SDave Cobbley
571eb8dc403SDave Cobbley            self._create_partition(self.path, part.type,
572eb8dc403SDave Cobbley                                   parted_fs_type, part.start, part.size_sec)
573eb8dc403SDave Cobbley
5748e7b46e2SPatrick Williams            if self.ptable_format == "gpt" and (part.part_name or part.label):
5758e7b46e2SPatrick Williams                partition_label = part.part_name if part.part_name else part.label
576eb8dc403SDave Cobbley                logger.debug("partition %d: set name to %s",
5778e7b46e2SPatrick Williams                             part.num, partition_label)
578eb8dc403SDave Cobbley                exec_native_cmd("sgdisk --change-name=%d:%s %s" % \
5798e7b46e2SPatrick Williams                                         (part.num, partition_label,
580eb8dc403SDave Cobbley                                          self.path), self.native_sysroot)
581eb8dc403SDave Cobbley
582eb8dc403SDave Cobbley            if part.part_type:
583eb8dc403SDave Cobbley                logger.debug("partition %d: set type UID to %s",
584eb8dc403SDave Cobbley                             part.num, part.part_type)
585eb8dc403SDave Cobbley                exec_native_cmd("sgdisk --typecode=%d:%s %s" % \
586eb8dc403SDave Cobbley                                         (part.num, part.part_type,
587eb8dc403SDave Cobbley                                          self.path), self.native_sysroot)
588eb8dc403SDave Cobbley
589eb8dc403SDave Cobbley            if part.uuid and self.ptable_format == "gpt":
590eb8dc403SDave Cobbley                logger.debug("partition %d: set UUID to %s",
591eb8dc403SDave Cobbley                             part.num, part.uuid)
592eb8dc403SDave Cobbley                exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \
593eb8dc403SDave Cobbley                                (part.num, part.uuid, self.path),
594eb8dc403SDave Cobbley                                self.native_sysroot)
595eb8dc403SDave Cobbley
596eb8dc403SDave Cobbley            if part.active:
597eb8dc403SDave Cobbley                flag_name = "legacy_boot" if self.ptable_format == 'gpt' else "boot"
598eb8dc403SDave Cobbley                logger.debug("Set '%s' flag for partition '%s' on disk '%s'",
599eb8dc403SDave Cobbley                             flag_name, part.num, self.path)
600eb8dc403SDave Cobbley                exec_native_cmd("parted -s %s set %d %s on" % \
601eb8dc403SDave Cobbley                                (self.path, part.num, flag_name),
602eb8dc403SDave Cobbley                                self.native_sysroot)
603eb8dc403SDave Cobbley            if part.system_id:
604eb8dc403SDave Cobbley                exec_native_cmd("sfdisk --part-type %s %s %s" % \
605eb8dc403SDave Cobbley                                (self.path, part.num, part.system_id),
606eb8dc403SDave Cobbley                                self.native_sysroot)
607eb8dc403SDave Cobbley
608*e760df85SPatrick Williams            if part.hidden and self.ptable_format == "gpt":
609*e760df85SPatrick Williams                logger.debug("Set hidden attribute for partition '%s' on disk '%s'",
610*e760df85SPatrick Williams                             part.num, self.path)
611*e760df85SPatrick Williams                exec_native_cmd("sfdisk --part-attrs %s %s RequiredPartition" % \
612*e760df85SPatrick Williams                                (self.path, part.num),
613*e760df85SPatrick Williams                                self.native_sysroot)
614*e760df85SPatrick Williams
615eb8dc403SDave Cobbley    def cleanup(self):
61682c905dcSAndrew Geissler        pass
617eb8dc403SDave Cobbley
618eb8dc403SDave Cobbley    def assemble(self):
619eb8dc403SDave Cobbley        logger.debug("Installing partitions")
620eb8dc403SDave Cobbley
621eb8dc403SDave Cobbley        for part in self.partitions:
622eb8dc403SDave Cobbley            source = part.source_file
623eb8dc403SDave Cobbley            if source:
624eb8dc403SDave Cobbley                # install source_file contents into a partition
625eb8dc403SDave Cobbley                sparse_copy(source, self.path, seek=part.start * self.sector_size)
626eb8dc403SDave Cobbley
627eb8dc403SDave Cobbley                logger.debug("Installed %s in partition %d, sectors %d-%d, "
628eb8dc403SDave Cobbley                             "size %d sectors", source, part.num, part.start,
629eb8dc403SDave Cobbley                             part.start + part.size_sec - 1, part.size_sec)
630eb8dc403SDave Cobbley
631eb8dc403SDave Cobbley                partimage = self.path + '.p%d' % part.num
632595f6308SAndrew Geissler                os.rename(source, partimage)
633eb8dc403SDave Cobbley                self.partimages.append(partimage)
634