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