diff options
Diffstat (limited to 'scripts/lib/wic')
-rw-r--r-- | scripts/lib/wic/canned-wks/efi-bootdisk.wks.in | 2 | ||||
-rw-r--r-- | scripts/lib/wic/canned-wks/qemuloongarch.wks | 3 | ||||
-rw-r--r-- | scripts/lib/wic/canned-wks/qemux86-directdisk.wks | 2 | ||||
-rw-r--r-- | scripts/lib/wic/engine.py | 6 | ||||
-rw-r--r-- | scripts/lib/wic/filemap.py | 7 | ||||
-rw-r--r-- | scripts/lib/wic/help.py | 12 | ||||
-rw-r--r-- | scripts/lib/wic/ksparser.py | 7 | ||||
-rw-r--r-- | scripts/lib/wic/misc.py | 14 | ||||
-rw-r--r-- | scripts/lib/wic/partition.py | 50 | ||||
-rw-r--r-- | scripts/lib/wic/pluginbase.py | 8 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/imager/direct.py | 129 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/bootimg-efi.py | 175 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/bootimg-partition.py | 9 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/bootimg-pcbios.py | 6 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/empty.py | 59 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/isoimage-isohybrid.py | 2 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/rawcopy.py | 36 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/source/rootfs.py | 11 |
18 files changed, 436 insertions, 102 deletions
diff --git a/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in b/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in index 7300e65e32..2fd286ff98 100644 --- a/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in +++ b/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in @@ -1,3 +1,3 @@ bootloader --ptable gpt -part /boot --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --label boot --active --align 1024 --use-uuid --overhead-factor 1.0 +part /boot --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --label boot --active --align 1024 --use-uuid --overhead-factor 1.1 part / --source rootfs --fstype=ext4 --label root --align 1024 --exclude-path boot/ diff --git a/scripts/lib/wic/canned-wks/qemuloongarch.wks b/scripts/lib/wic/canned-wks/qemuloongarch.wks new file mode 100644 index 0000000000..8465c7a8c0 --- /dev/null +++ b/scripts/lib/wic/canned-wks/qemuloongarch.wks @@ -0,0 +1,3 @@ +# short-description: Create qcow2 image for LoongArch QEMU machines + +part / --source rootfs --fstype=ext4 --label root --align 4096 --size 5G diff --git a/scripts/lib/wic/canned-wks/qemux86-directdisk.wks b/scripts/lib/wic/canned-wks/qemux86-directdisk.wks index 22b45217f1..808997611a 100644 --- a/scripts/lib/wic/canned-wks/qemux86-directdisk.wks +++ b/scripts/lib/wic/canned-wks/qemux86-directdisk.wks @@ -4,5 +4,5 @@ include common.wks.inc -bootloader --timeout=0 --append="rw oprofile.timer=1 rootfstype=ext4 " +bootloader --timeout=0 --append="rw oprofile.timer=1 rootfstype=ext4 console=tty console=ttyS0 " diff --git a/scripts/lib/wic/engine.py b/scripts/lib/wic/engine.py index 018815b966..674ccfc244 100644 --- a/scripts/lib/wic/engine.py +++ b/scripts/lib/wic/engine.py @@ -19,10 +19,10 @@ import os import tempfile import json import subprocess +import shutil import re from collections import namedtuple, OrderedDict -from distutils.spawn import find_executable from wic import WicError from wic.filemap import sparse_copy @@ -245,7 +245,7 @@ class Disk: for path in pathlist.split(':'): self.paths = "%s%s:%s" % (native_sysroot, path, self.paths) - self.parted = find_executable("parted", self.paths) + self.parted = shutil.which("parted", path=self.paths) if not self.parted: raise WicError("Can't find executable parted") @@ -283,7 +283,7 @@ class Disk: "resize2fs", "mkswap", "mkdosfs", "debugfs","blkid"): aname = "_%s" % name if aname not in self.__dict__: - setattr(self, aname, find_executable(name, self.paths)) + setattr(self, aname, shutil.which(name, path=self.paths)) if aname not in self.__dict__ or self.__dict__[aname] is None: raise WicError("Can't find executable '{}'".format(name)) return self.__dict__[aname] diff --git a/scripts/lib/wic/filemap.py b/scripts/lib/wic/filemap.py index 4d9da28172..85b39d5d74 100644 --- a/scripts/lib/wic/filemap.py +++ b/scripts/lib/wic/filemap.py @@ -46,6 +46,13 @@ def get_block_size(file_obj): bsize = stat.st_blksize else: raise IOError("Unable to determine block size") + + # The logic in this script only supports a maximum of a 4KB + # block size + max_block_size = 4 * 1024 + if bsize > max_block_size: + bsize = max_block_size + return bsize class ErrorNotSupp(Exception): diff --git a/scripts/lib/wic/help.py b/scripts/lib/wic/help.py index 991907d3f5..163535e431 100644 --- a/scripts/lib/wic/help.py +++ b/scripts/lib/wic/help.py @@ -840,8 +840,8 @@ DESCRIPTION meanings. The commands are based on the Fedora kickstart documentation but with modifications to reflect wic capabilities. - http://fedoraproject.org/wiki/Anaconda/Kickstart#part_or_partition - http://fedoraproject.org/wiki/Anaconda/Kickstart#bootloader + https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#part-or-partition + https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#bootloader Commands @@ -940,6 +940,12 @@ DESCRIPTION quotes. If not specified, the default string is "defaults". + --fspassno: Specifies the order in which filesystem checks are done + at boot time by fsck. See fs_passno parameter of + fstab(5). This parameter will be copied into the + /etc/fstab file of the installed system. If not + specified the default value of "0" will be used. + --label label: Specifies the label to give to the filesystem to be made on the partition. If the given label is already in use by another filesystem, @@ -1112,7 +1118,7 @@ COMMAND: TOPIC: overview - Presents an overall overview of Wic plugins - Presents an overview and API for Wic plugins - kickstart - Presents a Wic kicstart file reference + kickstart - Presents a Wic kickstart file reference Examples: diff --git a/scripts/lib/wic/ksparser.py b/scripts/lib/wic/ksparser.py index 0df9eb0d05..7ef3dc83dd 100644 --- a/scripts/lib/wic/ksparser.py +++ b/scripts/lib/wic/ksparser.py @@ -155,10 +155,11 @@ class KickStart(): part.add_argument('--change-directory') part.add_argument("--extra-space", type=sizetype("M")) part.add_argument('--fsoptions', dest='fsopts') + part.add_argument('--fspassno', dest='fspassno') part.add_argument('--fstype', default='vfat', choices=('ext2', 'ext3', 'ext4', 'btrfs', 'squashfs', 'vfat', 'msdos', 'erofs', - 'swap')) + 'swap', 'none')) part.add_argument('--mkfs-extraopts', default='') part.add_argument('--label') part.add_argument('--use-label', action='store_true') @@ -170,6 +171,7 @@ class KickStart(): part.add_argument('--rootfs-dir') part.add_argument('--type', default='primary', choices = ('primary', 'logical')) + part.add_argument('--hidden', action='store_true') # --size and --fixed-size cannot be specified together; options # ----extra-space and --overhead-factor should also raise a parser @@ -186,11 +188,12 @@ class KickStart(): part.add_argument('--uuid') part.add_argument('--fsuuid') part.add_argument('--no-fstab-update', action='store_true') + part.add_argument('--mbr', action='store_true') bootloader = subparsers.add_parser('bootloader') bootloader.add_argument('--append') bootloader.add_argument('--configfile') - bootloader.add_argument('--ptable', choices=('msdos', 'gpt'), + bootloader.add_argument('--ptable', choices=('msdos', 'gpt', 'gpt-hybrid'), default='msdos') bootloader.add_argument('--timeout', type=int) bootloader.add_argument('--source') diff --git a/scripts/lib/wic/misc.py b/scripts/lib/wic/misc.py index 57c042c503..1a7c140fa6 100644 --- a/scripts/lib/wic/misc.py +++ b/scripts/lib/wic/misc.py @@ -16,16 +16,16 @@ import logging import os import re import subprocess +import shutil from collections import defaultdict -from distutils import spawn from wic import WicError logger = logging.getLogger('wic') # executable -> recipe pairs for exec_native_cmd -NATIVE_RECIPES = {"bmaptool": "bmap-tools", +NATIVE_RECIPES = {"bmaptool": "bmaptool", "dumpe2fs": "e2fsprogs", "grub-mkimage": "grub-efi", "isohybrid": "syslinux", @@ -36,6 +36,7 @@ NATIVE_RECIPES = {"bmaptool": "bmap-tools", "mkdosfs": "dosfstools", "mkisofs": "cdrtools", "mkfs.btrfs": "btrfs-tools", + "mkfs.erofs": "erofs-utils", "mkfs.ext2": "e2fsprogs", "mkfs.ext3": "e2fsprogs", "mkfs.ext4": "e2fsprogs", @@ -122,7 +123,7 @@ def find_executable(cmd, paths): if provided and "%s-native" % recipe in provided: return True - return spawn.find_executable(cmd, paths) + return shutil.which(cmd, path=paths) def exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""): """ @@ -140,11 +141,12 @@ def exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""): cmd_and_args = pseudo + cmd_and_args hosttools_dir = get_bitbake_var("HOSTTOOLS_DIR") + target_sys = get_bitbake_var("TARGET_SYS") - native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/bin:%s" % \ + native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/usr/bin/%s:%s/bin:%s" % \ (native_sysroot, native_sysroot, - native_sysroot, native_sysroot, - hosttools_dir) + native_sysroot, native_sysroot, target_sys, + native_sysroot, hosttools_dir) native_cmd_and_args = "export PATH=%s:$PATH;%s" % \ (native_paths, cmd_and_args) diff --git a/scripts/lib/wic/partition.py b/scripts/lib/wic/partition.py index a25834048e..795707ec5d 100644 --- a/scripts/lib/wic/partition.py +++ b/scripts/lib/wic/partition.py @@ -33,6 +33,7 @@ class Partition(): self.include_path = args.include_path self.change_directory = args.change_directory self.fsopts = args.fsopts + self.fspassno = args.fspassno self.fstype = args.fstype self.label = args.label self.use_label = args.use_label @@ -58,6 +59,8 @@ class Partition(): self.updated_fstab_path = None self.has_fstab = False self.update_fstab_in_rootfs = False + self.hidden = args.hidden + self.mbr = args.mbr self.lineno = lineno self.source_file = "" @@ -132,6 +135,8 @@ class Partition(): self.update_fstab_in_rootfs = True if not self.source: + if self.fstype == "none" or self.no_table: + return if not self.size and not self.fixed_size: raise WicError("The %s partition has a size of zero. Please " "specify a non-zero --size/--fixed-size for that " @@ -171,7 +176,7 @@ class Partition(): # Split sourceparams string of the form key1=val1[,key2=val2,...] # into a dict. Also accepts valueless keys i.e. without = splitted = self.sourceparams.split(',') - srcparams_dict = dict(par.split('=', 1) for par in splitted if par) + srcparams_dict = dict((par.split('=', 1) + [None])[:2] for par in splitted if par) plugin = PluginMgr.get_plugins('source')[self.source] plugin.do_configure_partition(self, srcparams_dict, creator, @@ -279,6 +284,20 @@ class Partition(): extraopts = self.mkfs_extraopts or "-F -i 8192" + if os.getenv('SOURCE_DATE_EPOCH'): + sde_time = int(os.getenv('SOURCE_DATE_EPOCH')) + if pseudo: + pseudo = "export E2FSPROGS_FAKE_TIME=%s;%s " % (sde_time, pseudo) + else: + pseudo = "export E2FSPROGS_FAKE_TIME=%s; " % sde_time + + # Set hash_seed to generate deterministic directory indexes + namespace = uuid.UUID("e7429877-e7b3-4a68-a5c9-2f2fdf33d460") + if self.fsuuid: + namespace = uuid.UUID(self.fsuuid) + hash_seed = str(uuid.uuid5(namespace, str(sde_time))) + extraopts += " -E hash_seed=%s" % hash_seed + label_str = "" if self.label: label_str = "-L %s" % self.label @@ -299,6 +318,30 @@ class Partition(): mkfs_cmd = "fsck.%s -pvfD %s" % (self.fstype, rootfs) exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo) + if os.getenv('SOURCE_DATE_EPOCH'): + sde_time = hex(int(os.getenv('SOURCE_DATE_EPOCH'))) + debugfs_script_path = os.path.join(cr_workdir, "debugfs_script") + files = [] + for root, dirs, others in os.walk(rootfs_dir): + base = root.replace(rootfs_dir, "").rstrip(os.sep) + files += [ "/" if base == "" else base ] + files += [ base + "/" + n for n in dirs + others ] + with open(debugfs_script_path, "w") as f: + f.write("set_current_time %s\n" % (sde_time)) + if self.updated_fstab_path and self.has_fstab and not self.no_fstab_update: + f.write("set_inode_field /etc/fstab mtime %s\n" % (sde_time)) + f.write("set_inode_field /etc/fstab mtime_extra 0\n") + for file in set(files): + for time in ["atime", "ctime", "crtime"]: + f.write("set_inode_field \"%s\" %s %s\n" % (file, time, sde_time)) + f.write("set_inode_field \"%s\" %s_extra 0\n" % (file, time)) + for time in ["wtime", "mkfs_time", "lastcheck"]: + f.write("set_super_value %s %s\n" % (time, sde_time)) + for time in ["mtime", "first_error_time", "last_error_time"]: + f.write("set_super_value %s 0\n" % (time)) + debugfs_cmd = "debugfs -w -f %s %s" % (debugfs_script_path, rootfs) + exec_native_cmd(debugfs_cmd, native_sysroot) + self.check_for_Y2038_problem(rootfs, native_sysroot) def prepare_rootfs_btrfs(self, rootfs, cr_workdir, oe_builddir, rootfs_dir, @@ -352,7 +395,7 @@ class Partition(): exec_native_cmd(mcopy_cmd, native_sysroot) if self.updated_fstab_path and self.has_fstab and not self.no_fstab_update: - mcopy_cmd = "mcopy -i %s %s ::/etc/fstab" % (rootfs, self.updated_fstab_path) + mcopy_cmd = "mcopy -m -i %s %s ::/etc/fstab" % (rootfs, self.updated_fstab_path) exec_native_cmd(mcopy_cmd, native_sysroot) chmod_cmd = "chmod 644 %s" % rootfs @@ -380,6 +423,9 @@ class Partition(): (extraopts, self.fsuuid, rootfs, rootfs_dir) exec_native_cmd(erofs_cmd, native_sysroot, pseudo=pseudo) + def prepare_empty_partition_none(self, rootfs, oe_builddir, native_sysroot): + pass + def prepare_empty_partition_ext(self, rootfs, oe_builddir, native_sysroot): """ diff --git a/scripts/lib/wic/pluginbase.py b/scripts/lib/wic/pluginbase.py index d9b4e57747..b64568339b 100644 --- a/scripts/lib/wic/pluginbase.py +++ b/scripts/lib/wic/pluginbase.py @@ -9,9 +9,11 @@ __all__ = ['ImagerPlugin', 'SourcePlugin'] import os import logging +import types from collections import defaultdict -from importlib.machinery import SourceFileLoader +import importlib +import importlib.util from wic import WicError from wic.misc import get_bitbake_var @@ -54,7 +56,9 @@ class PluginMgr: mname = fname[:-3] mpath = os.path.join(ppath, fname) logger.debug("loading plugin module %s", mpath) - SourceFileLoader(mname, mpath).load_module() + spec = importlib.util.spec_from_file_location(mname, mpath) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) return PLUGINS.get(ptype) diff --git a/scripts/lib/wic/plugins/imager/direct.py b/scripts/lib/wic/plugins/imager/direct.py index 495518fac8..a1d152659b 100644 --- a/scripts/lib/wic/plugins/imager/direct.py +++ b/scripts/lib/wic/plugins/imager/direct.py @@ -117,7 +117,7 @@ class DirectPlugin(ImagerPlugin): updated = False for part in self.parts: if not part.realnum or not part.mountpoint \ - or part.mountpoint == "/" or not part.mountpoint.startswith('/'): + or part.mountpoint == "/" or not (part.mountpoint.startswith('/') or part.mountpoint == "swap"): continue if part.use_uuid: @@ -138,8 +138,9 @@ class DirectPlugin(ImagerPlugin): device_name = "/dev/%s%s%d" % (part.disk, prefix, part.realnum) opts = part.fsopts if part.fsopts else "defaults" + passno = part.fspassno if part.fspassno else "0" line = "\t".join([device_name, part.mountpoint, part.fstype, - opts, "0", "0"]) + "\n" + opts, "0", passno]) + "\n" fstab_lines.append(line) updated = True @@ -148,6 +149,9 @@ class DirectPlugin(ImagerPlugin): self.updated_fstab_path = os.path.join(self.workdir, "fstab") with open(self.updated_fstab_path, "w") as f: f.writelines(fstab_lines) + if os.getenv('SOURCE_DATE_EPOCH'): + fstab_time = int(os.getenv('SOURCE_DATE_EPOCH')) + os.utime(self.updated_fstab_path, (fstab_time, fstab_time)) def _full_path(self, path, name, extention): """ Construct full file path to a file we generate. """ @@ -259,6 +263,8 @@ class DirectPlugin(ImagerPlugin): if part.mountpoint == "/": if part.uuid: return "PARTUUID=%s" % part.uuid + elif part.label and self.ptable_format != 'msdos': + return "PARTLABEL=%s" % part.label else: suffix = 'p' if part.disk.startswith('mmcblk') else '' return "/dev/%s%s%-d" % (part.disk, suffix, part.realnum) @@ -307,7 +313,10 @@ class PartitionedImage(): # all partitions (in bytes) self.ptable_format = ptable_format # Partition table format # Disk system identifier - self.identifier = random.SystemRandom().randint(1, 0xffffffff) + if os.getenv('SOURCE_DATE_EPOCH'): + self.identifier = random.Random(int(os.getenv('SOURCE_DATE_EPOCH'))).randint(1, 0xffffffff) + else: + self.identifier = random.SystemRandom().randint(1, 0xffffffff) self.partitions = partitions self.partimages = [] @@ -333,7 +342,7 @@ class PartitionedImage(): # generate parition and filesystem UUIDs for part in self.partitions: if not part.uuid and part.use_uuid: - if self.ptable_format == 'gpt': + if self.ptable_format in ('gpt', 'gpt-hybrid'): part.uuid = str(uuid.uuid4()) else: # msdos partition table part.uuid = '%08x-%02d' % (self.identifier, part.realnum) @@ -389,6 +398,10 @@ class PartitionedImage(): raise WicError("setting custom partition type is not " \ "implemented for msdos partitions") + if part.mbr and self.ptable_format != 'gpt-hybrid': + raise WicError("Partition may only be included in MBR with " \ + "a gpt-hybrid partition table") + # Get the disk where the partition is located self.numpart += 1 if not part.no_table: @@ -397,7 +410,7 @@ class PartitionedImage(): if self.numpart == 1: if self.ptable_format == "msdos": overhead = MBR_OVERHEAD - elif self.ptable_format == "gpt": + elif self.ptable_format in ("gpt", "gpt-hybrid"): overhead = GPT_OVERHEAD # Skip one sector required for the partitioning scheme overhead @@ -481,7 +494,7 @@ class PartitionedImage(): # Once all the partitions have been layed out, we can calculate the # minumim disk size self.min_size = self.offset - if self.ptable_format == "gpt": + if self.ptable_format in ("gpt", "gpt-hybrid"): self.min_size += GPT_OVERHEAD self.min_size *= self.sector_size @@ -502,22 +515,49 @@ class PartitionedImage(): return exec_native_cmd(cmd, self.native_sysroot) + def _write_identifier(self, device, identifier): + logger.debug("Set disk identifier %x", identifier) + with open(device, 'r+b') as img: + img.seek(0x1B8) + img.write(identifier.to_bytes(4, 'little')) + + def _make_disk(self, device, ptable_format, min_size): + logger.debug("Creating sparse file %s", device) + with open(device, 'w') as sparse: + os.ftruncate(sparse.fileno(), min_size) + + logger.debug("Initializing partition table for %s", device) + exec_native_cmd("parted -s %s mklabel %s" % (device, ptable_format), + self.native_sysroot) + + def _write_disk_guid(self): + if self.ptable_format in ('gpt', 'gpt-hybrid'): + if os.getenv('SOURCE_DATE_EPOCH'): + self.disk_guid = uuid.UUID(int=int(os.getenv('SOURCE_DATE_EPOCH'))) + else: + self.disk_guid = uuid.uuid4() + + logger.debug("Set disk guid %s", self.disk_guid) + sfdisk_cmd = "sfdisk --disk-id %s %s" % (self.path, self.disk_guid) + exec_native_cmd(sfdisk_cmd, self.native_sysroot) + def create(self): - logger.debug("Creating sparse file %s", self.path) - with open(self.path, 'w') as sparse: - os.ftruncate(sparse.fileno(), self.min_size) + self._make_disk(self.path, + "gpt" if self.ptable_format == "gpt-hybrid" else self.ptable_format, + self.min_size) - logger.debug("Initializing partition table for %s", self.path) - exec_native_cmd("parted -s %s mklabel %s" % - (self.path, self.ptable_format), self.native_sysroot) + self._write_identifier(self.path, self.identifier) + self._write_disk_guid() - logger.debug("Set disk identifier %x", self.identifier) - with open(self.path, 'r+b') as img: - img.seek(0x1B8) - img.write(self.identifier.to_bytes(4, 'little')) + if self.ptable_format == "gpt-hybrid": + mbr_path = self.path + ".mbr" + self._make_disk(mbr_path, "msdos", self.min_size) + self._write_identifier(mbr_path, self.identifier) logger.debug("Creating partitions") + hybrid_mbr_part_num = 0 + for part in self.partitions: if part.num == 0: continue @@ -562,11 +602,19 @@ class PartitionedImage(): self._create_partition(self.path, part.type, parted_fs_type, part.start, part.size_sec) - if part.part_name: + if self.ptable_format == "gpt-hybrid" and part.mbr: + hybrid_mbr_part_num += 1 + if hybrid_mbr_part_num > 4: + raise WicError("Extended MBR partitions are not supported in hybrid MBR") + self._create_partition(mbr_path, "primary", + parted_fs_type, part.start, part.size_sec) + + if self.ptable_format in ("gpt", "gpt-hybrid") and (part.part_name or part.label): + partition_label = part.part_name if part.part_name else part.label logger.debug("partition %d: set name to %s", - part.num, part.part_name) + part.num, partition_label) exec_native_cmd("sgdisk --change-name=%d:%s %s" % \ - (part.num, part.part_name, + (part.num, partition_label, self.path), self.native_sysroot) if part.part_type: @@ -576,32 +624,55 @@ class PartitionedImage(): (part.num, part.part_type, self.path), self.native_sysroot) - if part.uuid and self.ptable_format == "gpt": + if part.uuid and self.ptable_format in ("gpt", "gpt-hybrid"): logger.debug("partition %d: set UUID to %s", part.num, part.uuid) exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \ (part.num, part.uuid, self.path), self.native_sysroot) - if part.label and self.ptable_format == "gpt": - logger.debug("partition %d: set name to %s", - part.num, part.label) - exec_native_cmd("parted -s %s name %d %s" % \ - (self.path, part.num, part.label), - self.native_sysroot) - if part.active: - flag_name = "legacy_boot" if self.ptable_format == 'gpt' else "boot" + flag_name = "legacy_boot" if self.ptable_format in ('gpt', 'gpt-hybrid') else "boot" logger.debug("Set '%s' flag for partition '%s' on disk '%s'", flag_name, part.num, self.path) exec_native_cmd("parted -s %s set %d %s on" % \ (self.path, part.num, flag_name), self.native_sysroot) + if self.ptable_format == 'gpt-hybrid' and part.mbr: + exec_native_cmd("parted -s %s set %d %s on" % \ + (mbr_path, hybrid_mbr_part_num, "boot"), + self.native_sysroot) if part.system_id: exec_native_cmd("sfdisk --part-type %s %s %s" % \ (self.path, part.num, part.system_id), self.native_sysroot) + if part.hidden and self.ptable_format == "gpt": + logger.debug("Set hidden attribute for partition '%s' on disk '%s'", + part.num, self.path) + exec_native_cmd("sfdisk --part-attrs %s %s RequiredPartition" % \ + (self.path, part.num), + self.native_sysroot) + + if self.ptable_format == "gpt-hybrid": + # Write a protective GPT partition + hybrid_mbr_part_num += 1 + if hybrid_mbr_part_num > 4: + raise WicError("Extended MBR partitions are not supported in hybrid MBR") + + # parted cannot directly create a protective GPT partition, so + # create with an arbitrary type, then change it to the correct type + # with sfdisk + self._create_partition(mbr_path, "primary", "fat32", 1, GPT_OVERHEAD) + exec_native_cmd("sfdisk --part-type %s %d 0xee" % (mbr_path, hybrid_mbr_part_num), + self.native_sysroot) + + # Copy hybrid MBR + with open(mbr_path, "rb") as mbr_file: + with open(self.path, "r+b") as image_file: + mbr = mbr_file.read(512) + image_file.write(mbr) + def cleanup(self): pass @@ -619,5 +690,5 @@ class PartitionedImage(): part.start + part.size_sec - 1, part.size_sec) partimage = self.path + '.p%d' % part.num - bb.utils.rename(source, partimage) + os.rename(source, partimage) self.partimages.append(partimage) diff --git a/scripts/lib/wic/plugins/source/bootimg-efi.py b/scripts/lib/wic/plugins/source/bootimg-efi.py index 0391aebdc8..13a9cddf4e 100644 --- a/scripts/lib/wic/plugins/source/bootimg-efi.py +++ b/scripts/lib/wic/plugins/source/bootimg-efi.py @@ -35,6 +35,26 @@ class BootimgEFIPlugin(SourcePlugin): name = 'bootimg-efi' @classmethod + def _copy_additional_files(cls, hdddir, initrd, dtb): + bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") + if not bootimg_dir: + raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting") + + if initrd: + initrds = initrd.split(';') + for rd in initrds: + cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir) + exec_cmd(cp_cmd, True) + else: + logger.debug("Ignoring missing initrd") + + if dtb: + if ';' in dtb: + raise WicError("Only one DTB supported, exiting") + cp_cmd = "cp %s/%s %s" % (bootimg_dir, dtb, hdddir) + exec_cmd(cp_cmd, True) + + @classmethod def do_configure_grubefi(cls, hdddir, creator, cr_workdir, source_params): """ Create loader-specific (grub-efi) config @@ -53,18 +73,9 @@ class BootimgEFIPlugin(SourcePlugin): "get it from %s." % configfile) initrd = source_params.get('initrd') + dtb = source_params.get('dtb') - if initrd: - bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") - if not bootimg_dir: - raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting") - - initrds = initrd.split(';') - for rd in initrds: - cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir) - exec_cmd(cp_cmd, True) - else: - logger.debug("Ignoring missing initrd") + cls._copy_additional_files(hdddir, initrd, dtb) if not custom_cfg: # Create grub configuration using parameters from wks file @@ -98,6 +109,9 @@ class BootimgEFIPlugin(SourcePlugin): grubefi_conf += " /%s" % rd grubefi_conf += "\n" + if dtb: + grubefi_conf += "devicetree /%s\n" % dtb + grubefi_conf += "}\n" logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg", @@ -119,25 +133,18 @@ class BootimgEFIPlugin(SourcePlugin): bootloader = creator.ks.bootloader + unified_image = source_params.get('create-unified-kernel-image') == "true" + loader_conf = "" - if source_params.get('create-unified-kernel-image') != "true": + if not unified_image: loader_conf += "default boot\n" loader_conf += "timeout %d\n" % bootloader.timeout initrd = source_params.get('initrd') + dtb = source_params.get('dtb') - if initrd and source_params.get('create-unified-kernel-image') != "true": - # obviously we need to have a common common deploy var - bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") - if not bootimg_dir: - raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting") - - initrds = initrd.split(';') - for rd in initrds: - cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir) - exec_cmd(cp_cmd, True) - else: - logger.debug("Ignoring missing initrd") + if not unified_image: + cls._copy_additional_files(hdddir, initrd, dtb) logger.debug("Writing systemd-boot config " "%s/hdd/boot/loader/loader.conf", cr_workdir) @@ -185,7 +192,10 @@ class BootimgEFIPlugin(SourcePlugin): for rd in initrds: boot_conf += "initrd /%s\n" % rd - if source_params.get('create-unified-kernel-image') != "true": + if dtb: + boot_conf += "devicetree /%s\n" % dtb + + if not unified_image: logger.debug("Writing systemd-boot config " "%s/hdd/boot/loader/entries/boot.conf", cr_workdir) cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w") @@ -210,6 +220,8 @@ class BootimgEFIPlugin(SourcePlugin): cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params) elif source_params['loader'] == 'systemd-boot': cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params) + elif source_params['loader'] == 'uefi-kernel': + pass else: raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader']) except KeyError: @@ -326,25 +338,72 @@ class BootimgEFIPlugin(SourcePlugin): exec_cmd(install_cmd) staging_dir_host = get_bitbake_var("STAGING_DIR_HOST") + target_sys = get_bitbake_var("TARGET_SYS") + + objdump_cmd = "%s-objdump" % target_sys + objdump_cmd += " -p %s" % efi_stub + objdump_cmd += " | awk '{ if ($1 == \"SectionAlignment\"){print $2} }'" + + ret, align_str = exec_native_cmd(objdump_cmd, native_sysroot) + align = int(align_str, 16) + + objdump_cmd = "%s-objdump" % target_sys + objdump_cmd += " -h %s | tail -2" % efi_stub + ret, output = exec_native_cmd(objdump_cmd, native_sysroot) + + offset = int(output.split()[2], 16) + int(output.split()[3], 16) + + osrel_off = offset + align - offset % align + osrel_path = "%s/usr/lib/os-release" % staging_dir_host + osrel_sz = os.stat(osrel_path).st_size + + cmdline_off = osrel_off + osrel_sz + cmdline_off = cmdline_off + align - cmdline_off % align + cmdline_sz = os.stat(cmdline.name).st_size + + dtb_off = cmdline_off + cmdline_sz + dtb_off = dtb_off + align - dtb_off % align + + dtb = source_params.get('dtb') + if dtb: + if ';' in dtb: + raise WicError("Only one DTB supported, exiting") + dtb_path = "%s/%s" % (deploy_dir, dtb) + dtb_params = '--add-section .dtb=%s --change-section-vma .dtb=0x%x' % \ + (dtb_path, dtb_off) + linux_off = dtb_off + os.stat(dtb_path).st_size + linux_off = linux_off + align - linux_off % align + else: + dtb_params = '' + linux_off = dtb_off + + linux_path = "%s/%s" % (staging_kernel_dir, kernel) + linux_sz = os.stat(linux_path).st_size + + initrd_off = linux_off + linux_sz + initrd_off = initrd_off + align - initrd_off % align # https://www.freedesktop.org/software/systemd/man/systemd-stub.html - objcopy_cmd = "objcopy \ - --add-section .osrel=%s --change-section-vma .osrel=0x20000 \ - --add-section .cmdline=%s --change-section-vma .cmdline=0x30000 \ - --add-section .linux=%s --change-section-vma .linux=0x2000000 \ - --add-section .initrd=%s --change-section-vma .initrd=0x3000000 \ - %s %s" % \ - ("%s/usr/lib/os-release" % staging_dir_host, - cmdline.name, - "%s/%s" % (staging_kernel_dir, kernel), - initrd.name, - efi_stub, - "%s/EFI/Linux/linux.efi" % hdddir) - exec_cmd(objcopy_cmd) + objcopy_cmd = "%s-objcopy" % target_sys + objcopy_cmd += " --enable-deterministic-archives" + objcopy_cmd += " --preserve-dates" + objcopy_cmd += " --add-section .osrel=%s" % osrel_path + objcopy_cmd += " --change-section-vma .osrel=0x%x" % osrel_off + objcopy_cmd += " --add-section .cmdline=%s" % cmdline.name + objcopy_cmd += " --change-section-vma .cmdline=0x%x" % cmdline_off + objcopy_cmd += dtb_params + objcopy_cmd += " --add-section .linux=%s" % linux_path + objcopy_cmd += " --change-section-vma .linux=0x%x" % linux_off + objcopy_cmd += " --add-section .initrd=%s" % initrd.name + objcopy_cmd += " --change-section-vma .initrd=0x%x" % initrd_off + objcopy_cmd += " %s %s/EFI/Linux/linux.efi" % (efi_stub, hdddir) + + exec_native_cmd(objcopy_cmd, native_sysroot) else: - install_cmd = "install -m 0644 %s/%s %s/%s" % \ - (staging_kernel_dir, kernel, hdddir, kernel) - exec_cmd(install_cmd) + if source_params.get('install-kernel-into-boot-dir') != 'false': + install_cmd = "install -m 0644 %s/%s %s/%s" % \ + (staging_kernel_dir, kernel, hdddir, kernel) + exec_cmd(install_cmd) if get_bitbake_var("IMAGE_EFI_BOOT_FILES"): for src_path, dst_path in cls.install_task: @@ -366,6 +425,28 @@ class BootimgEFIPlugin(SourcePlugin): for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]: cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:]) exec_cmd(cp_cmd, True) + elif source_params['loader'] == 'uefi-kernel': + kernel = get_bitbake_var("KERNEL_IMAGETYPE") + if not kernel: + raise WicError("Empty KERNEL_IMAGETYPE %s\n" % target) + target = get_bitbake_var("TARGET_SYS") + if not target: + raise WicError("Unknown arch (TARGET_SYS) %s\n" % target) + + if re.match("x86_64", target): + kernel_efi_image = "bootx64.efi" + elif re.match('i.86', target): + kernel_efi_image = "bootia32.efi" + elif re.match('aarch64', target): + kernel_efi_image = "bootaa64.efi" + elif re.match('arm', target): + kernel_efi_image = "bootarm.efi" + else: + raise WicError("UEFI stub kernel is incompatible with target %s" % target) + + for mod in [x for x in os.listdir(kernel_dir) if x.startswith(kernel)]: + cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, kernel_efi_image) + exec_cmd(cp_cmd, True) else: raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader']) @@ -377,6 +458,11 @@ class BootimgEFIPlugin(SourcePlugin): cp_cmd = "cp %s %s/" % (startup, hdddir) exec_cmd(cp_cmd, True) + for paths in part.include_path or []: + for path in paths: + cp_cmd = "cp -r %s %s/" % (path, hdddir) + exec_cmd(cp_cmd, True) + du_cmd = "du -bks %s" % hdddir out = exec_cmd(du_cmd) blocks = int(out.split()[0]) @@ -391,6 +477,13 @@ class BootimgEFIPlugin(SourcePlugin): logger.debug("Added %d extra blocks to %s to get to %d total blocks", extra_blocks, part.mountpoint, blocks) + # required for compatibility with certain devices expecting file system + # block count to be equal to partition block count + if blocks < part.fixed_size: + blocks = part.fixed_size + logger.debug("Overriding %s to %d total blocks for compatibility", + part.mountpoint, blocks) + # dosfs image, created by mkdosfs bootimg = "%s/boot.img" % cr_workdir diff --git a/scripts/lib/wic/plugins/source/bootimg-partition.py b/scripts/lib/wic/plugins/source/bootimg-partition.py index 5dbe2558d2..1071d1af3f 100644 --- a/scripts/lib/wic/plugins/source/bootimg-partition.py +++ b/scripts/lib/wic/plugins/source/bootimg-partition.py @@ -1,4 +1,6 @@ # +# Copyright OpenEmbedded Contributors +# # SPDX-License-Identifier: GPL-2.0-only # # DESCRIPTION @@ -30,6 +32,7 @@ class BootimgPartitionPlugin(SourcePlugin): """ name = 'bootimg-partition' + image_boot_files_var_name = 'IMAGE_BOOT_FILES' @classmethod def do_configure_partition(cls, part, source_params, cr, cr_workdir, @@ -54,12 +57,12 @@ class BootimgPartitionPlugin(SourcePlugin): else: var = "" - boot_files = get_bitbake_var("IMAGE_BOOT_FILES" + var) + boot_files = get_bitbake_var(cls.image_boot_files_var_name + var) if boot_files is not None: break if boot_files is None: - raise WicError('No boot files defined, IMAGE_BOOT_FILES unset for entry #%d' % part.lineno) + raise WicError('No boot files defined, %s unset for entry #%d' % (cls.image_boot_files_var_name, part.lineno)) logger.debug('Boot files: %s', boot_files) @@ -110,7 +113,7 @@ class BootimgPartitionPlugin(SourcePlugin): # Use a custom configuration for extlinux.conf extlinux_conf = custom_cfg logger.debug("Using custom configuration file " - "%s for extlinux.cfg", configfile) + "%s for extlinux.conf", configfile) else: raise WicError("configfile is specified but failed to " "get it from %s." % configfile) diff --git a/scripts/lib/wic/plugins/source/bootimg-pcbios.py b/scripts/lib/wic/plugins/source/bootimg-pcbios.py index 32e47f1831..a207a83530 100644 --- a/scripts/lib/wic/plugins/source/bootimg-pcbios.py +++ b/scripts/lib/wic/plugins/source/bootimg-pcbios.py @@ -122,7 +122,7 @@ class BootimgPcbiosPlugin(SourcePlugin): syslinux_conf += "DEFAULT boot\n" syslinux_conf += "LABEL boot\n" - kernel = "/vmlinuz" + kernel = "/" + get_bitbake_var("KERNEL_IMAGETYPE") syslinux_conf += "KERNEL " + kernel + "\n" syslinux_conf += "APPEND label=boot root=%s %s\n" % \ @@ -155,8 +155,8 @@ class BootimgPcbiosPlugin(SourcePlugin): kernel = "%s-%s.bin" % \ (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME")) - cmds = ("install -m 0644 %s/%s %s/vmlinuz" % - (staging_kernel_dir, kernel, hdddir), + cmds = ("install -m 0644 %s/%s %s/%s" % + (staging_kernel_dir, kernel, hdddir, get_bitbake_var("KERNEL_IMAGETYPE")), "install -m 444 %s/syslinux/ldlinux.sys %s/ldlinux.sys" % (bootimg_dir, hdddir), "install -m 0644 %s/syslinux/vesamenu.c32 %s/vesamenu.c32" % diff --git a/scripts/lib/wic/plugins/source/empty.py b/scripts/lib/wic/plugins/source/empty.py index 041617d648..4178912377 100644 --- a/scripts/lib/wic/plugins/source/empty.py +++ b/scripts/lib/wic/plugins/source/empty.py @@ -1,4 +1,6 @@ # +# Copyright OpenEmbedded Contributors +# # SPDX-License-Identifier: MIT # @@ -7,9 +9,19 @@ # To use it you must pass "empty" as argument for the "--source" parameter in # the wks file. For example: # part foo --source empty --ondisk sda --size="1024" --align 1024 +# +# The plugin supports writing zeros to the start of the +# partition. This is useful to overwrite old content like +# filesystem signatures which may be re-recognized otherwise. +# This feature can be enabled with +# '--sourceparams="[fill|size=<N>[S|s|K|k|M|G]][,][bs=<N>[S|s|K|k|M|G]]"' +# Conflicting or missing options throw errors. import logging +import os +from wic import WicError +from wic.ksparser import sizetype from wic.pluginbase import SourcePlugin logger = logging.getLogger('wic') @@ -17,6 +29,16 @@ logger = logging.getLogger('wic') class EmptyPartitionPlugin(SourcePlugin): """ Populate unformatted empty partition. + + The following sourceparams are supported: + - fill + Fill the entire partition with zeros. Requires '--fixed-size' option + to be set. + - size=<N>[S|s|K|k|M|G] + Set the first N bytes of the partition to zero. Default unit is 'K'. + - bs=<N>[S|s|K|k|M|G] + Write at most N bytes at a time during source file creation. + Defaults to '1M'. Default unit is 'K'. """ name = 'empty' @@ -29,4 +51,39 @@ class EmptyPartitionPlugin(SourcePlugin): Called to do the actual content population for a partition i.e. it 'prepares' the partition to be incorporated into the image. """ - return + get_byte_count = sizetype('K', True) + size = 0 + + if 'fill' in source_params and 'size' in source_params: + raise WicError("Conflicting source parameters 'fill' and 'size' specified, exiting.") + + # Set the size of the zeros to be written to the partition + if 'fill' in source_params: + if part.fixed_size == 0: + raise WicError("Source parameter 'fill' only works with the '--fixed-size' option, exiting.") + size = get_byte_count(part.fixed_size) + elif 'size' in source_params: + size = get_byte_count(source_params['size']) + + if size == 0: + # Nothing to do, create empty partition + return + + if 'bs' in source_params: + bs = get_byte_count(source_params['bs']) + else: + bs = get_byte_count('1M') + + # Create a binary file of the requested size filled with zeros + source_file = os.path.join(cr_workdir, 'empty-plugin-zeros%s.bin' % part.lineno) + if not os.path.exists(os.path.dirname(source_file)): + os.makedirs(os.path.dirname(source_file)) + + quotient, remainder = divmod(size, bs) + with open(source_file, 'wb') as file: + for _ in range(quotient): + file.write(bytearray(bs)) + file.write(bytearray(remainder)) + + part.size = (size + 1024 - 1) // 1024 # size in KB rounded up + part.source_file = source_file diff --git a/scripts/lib/wic/plugins/source/isoimage-isohybrid.py b/scripts/lib/wic/plugins/source/isoimage-isohybrid.py index afc9ea0f8f..607356ad13 100644 --- a/scripts/lib/wic/plugins/source/isoimage-isohybrid.py +++ b/scripts/lib/wic/plugins/source/isoimage-isohybrid.py @@ -1,4 +1,6 @@ # +# Copyright OpenEmbedded Contributors +# # SPDX-License-Identifier: GPL-2.0-only # # DESCRIPTION diff --git a/scripts/lib/wic/plugins/source/rawcopy.py b/scripts/lib/wic/plugins/source/rawcopy.py index fa7b1eb8ac..21903c2f23 100644 --- a/scripts/lib/wic/plugins/source/rawcopy.py +++ b/scripts/lib/wic/plugins/source/rawcopy.py @@ -1,9 +1,13 @@ # +# Copyright OpenEmbedded Contributors +# # SPDX-License-Identifier: GPL-2.0-only # import logging import os +import signal +import subprocess from wic import WicError from wic.pluginbase import SourcePlugin @@ -21,6 +25,10 @@ class RawCopyPlugin(SourcePlugin): @staticmethod def do_image_label(fstype, dst, label): + # don't create label when fstype is none + if fstype == 'none': + return + if fstype.startswith('ext'): cmd = 'tune2fs -L %s %s' % (label, dst) elif fstype in ('msdos', 'vfat'): @@ -38,6 +46,26 @@ class RawCopyPlugin(SourcePlugin): exec_cmd(cmd) + @staticmethod + def do_image_uncompression(src, dst, workdir): + def subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what + # non-Python subprocesses expect. + # SIGPIPE errors are known issues with gzip/bash + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + extension = os.path.splitext(src)[1] + decompressor = { + ".bz2": "bzip2", + ".gz": "gzip", + ".xz": "xz", + ".zst": "zstd -f", + }.get(extension) + if not decompressor: + raise WicError("Not supported compressor filename extension: %s" % extension) + cmd = "%s -dc %s > %s" % (decompressor, src, dst) + subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=workdir) + @classmethod def do_prepare_partition(cls, part, source_params, cr, cr_workdir, oe_builddir, bootimg_dir, kernel_dir, @@ -56,7 +84,13 @@ class RawCopyPlugin(SourcePlugin): if 'file' not in source_params: raise WicError("No file specified") - src = os.path.join(kernel_dir, source_params['file']) + if 'unpack' in source_params: + img = os.path.join(kernel_dir, source_params['file']) + src = os.path.join(cr_workdir, os.path.splitext(source_params['file'])[0]) + RawCopyPlugin.do_image_uncompression(img, src, cr_workdir) + else: + src = os.path.join(kernel_dir, source_params['file']) + dst = os.path.join(cr_workdir, "%s.%s" % (os.path.basename(source_params['file']), part.lineno)) if not os.path.exists(os.path.dirname(dst)): diff --git a/scripts/lib/wic/plugins/source/rootfs.py b/scripts/lib/wic/plugins/source/rootfs.py index 2e34e715ca..c990143c0d 100644 --- a/scripts/lib/wic/plugins/source/rootfs.py +++ b/scripts/lib/wic/plugins/source/rootfs.py @@ -35,7 +35,7 @@ class RootfsPlugin(SourcePlugin): @staticmethod def __validate_path(cmd, rootfs_dir, path): if os.path.isabs(path): - logger.error("%s: Must be relative: %s" % (cmd, orig_path)) + logger.error("%s: Must be relative: %s" % (cmd, path)) sys.exit(1) # Disallow climbing outside of parent directory using '..', @@ -43,14 +43,14 @@ class RootfsPlugin(SourcePlugin): # directory, or modify a directory outside OpenEmbedded). full_path = os.path.realpath(os.path.join(rootfs_dir, path)) if not full_path.startswith(os.path.realpath(rootfs_dir)): - logger.error("%s: Must point inside the rootfs:" % (cmd, path)) + logger.error("%s: Must point inside the rootfs: %s" % (cmd, path)) sys.exit(1) return full_path @staticmethod def __get_rootfs_dir(rootfs_dir): - if os.path.isdir(rootfs_dir): + if rootfs_dir and os.path.isdir(rootfs_dir): return os.path.realpath(rootfs_dir) image_rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", rootfs_dir) @@ -97,6 +97,9 @@ class RootfsPlugin(SourcePlugin): part.has_fstab = os.path.exists(os.path.join(part.rootfs_dir, "etc/fstab")) pseudo_dir = os.path.join(part.rootfs_dir, "../pseudo") if not os.path.lexists(pseudo_dir): + pseudo_dir = os.path.join(cls.__get_rootfs_dir(None), '../pseudo') + + if not os.path.lexists(pseudo_dir): logger.warn("%s folder does not exist. " "Usernames and permissions will be invalid " % pseudo_dir) pseudo_dir = None @@ -221,7 +224,7 @@ class RootfsPlugin(SourcePlugin): if part.update_fstab_in_rootfs and part.has_fstab and not part.no_fstab_update: fstab_path = os.path.join(new_rootfs, "etc/fstab") # Assume that fstab should always be owned by root with fixed permissions - install_cmd = "install -m 0644 %s %s" % (part.updated_fstab_path, fstab_path) + install_cmd = "install -m 0644 -p %s %s" % (part.updated_fstab_path, fstab_path) if new_pseudo: pseudo = cls.__get_pseudo(native_sysroot, new_rootfs, new_pseudo) else: |