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