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