summaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/wic')
-rw-r--r--scripts/lib/wic/__init__.py10
-rw-r--r--scripts/lib/wic/canned-wks/common.wks.inc3
-rw-r--r--scripts/lib/wic/canned-wks/directdisk-bootloader-config.cfg27
-rw-r--r--scripts/lib/wic/canned-wks/directdisk-bootloader-config.wks8
-rw-r--r--scripts/lib/wic/canned-wks/directdisk-gpt.wks10
-rw-r--r--scripts/lib/wic/canned-wks/directdisk-multi-rootfs.wks23
-rw-r--r--scripts/lib/wic/canned-wks/directdisk.wks8
-rw-r--r--scripts/lib/wic/canned-wks/efi-bootdisk.wks.in3
-rw-r--r--scripts/lib/wic/canned-wks/mkefidisk.wks11
-rw-r--r--scripts/lib/wic/canned-wks/mkhybridiso.wks7
-rw-r--r--scripts/lib/wic/canned-wks/qemuloongarch.wks3
-rw-r--r--scripts/lib/wic/canned-wks/qemuriscv.wks3
-rw-r--r--scripts/lib/wic/canned-wks/qemux86-directdisk.wks8
-rw-r--r--scripts/lib/wic/canned-wks/sdimage-bootpart.wks6
-rw-r--r--scripts/lib/wic/canned-wks/systemd-bootdisk.wks11
-rw-r--r--scripts/lib/wic/engine.py628
-rw-r--r--scripts/lib/wic/filemap.py583
-rw-r--r--scripts/lib/wic/help.py1148
-rw-r--r--scripts/lib/wic/ksparser.py298
-rw-r--r--scripts/lib/wic/misc.py266
-rw-r--r--scripts/lib/wic/partition.py542
-rw-r--r--scripts/lib/wic/pluginbase.py144
-rw-r--r--scripts/lib/wic/plugins/imager/direct.py694
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-biosplusefi.py213
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-efi.py507
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-partition.py197
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-pcbios.py209
-rw-r--r--scripts/lib/wic/plugins/source/empty.py89
-rw-r--r--scripts/lib/wic/plugins/source/isoimage-isohybrid.py463
-rw-r--r--scripts/lib/wic/plugins/source/rawcopy.py115
-rw-r--r--scripts/lib/wic/plugins/source/rootfs.py236
31 files changed, 0 insertions, 6473 deletions
diff --git a/scripts/lib/wic/__init__.py b/scripts/lib/wic/__init__.py
deleted file mode 100644
index 85567934ae..0000000000
--- a/scripts/lib/wic/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
1#!/usr/bin/env python3
2#
3# Copyright (c) 2007 Red Hat, Inc.
4# Copyright (c) 2011 Intel, Inc.
5#
6# SPDX-License-Identifier: GPL-2.0-only
7#
8
9class WicError(Exception):
10 pass
diff --git a/scripts/lib/wic/canned-wks/common.wks.inc b/scripts/lib/wic/canned-wks/common.wks.inc
deleted file mode 100644
index 89880b417b..0000000000
--- a/scripts/lib/wic/canned-wks/common.wks.inc
+++ /dev/null
@@ -1,3 +0,0 @@
1# This file is included into 3 canned wks files from this directory
2part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
3part / --source rootfs --use-uuid --fstype=ext4 --label platform --align 1024
diff --git a/scripts/lib/wic/canned-wks/directdisk-bootloader-config.cfg b/scripts/lib/wic/canned-wks/directdisk-bootloader-config.cfg
deleted file mode 100644
index c58e74a853..0000000000
--- a/scripts/lib/wic/canned-wks/directdisk-bootloader-config.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
1# This is an example configuration file for syslinux.
2TIMEOUT 50
3ALLOWOPTIONS 1
4SERIAL 0 115200
5PROMPT 0
6
7UI vesamenu.c32
8menu title Select boot options
9menu tabmsg Press [Tab] to edit, [Return] to select
10
11DEFAULT Graphics console boot
12
13LABEL Graphics console boot
14KERNEL /vmlinuz
15APPEND label=boot rootwait
16
17LABEL Serial console boot
18KERNEL /vmlinuz
19APPEND label=boot rootwait console=ttyS0,115200
20
21LABEL Graphics console install
22KERNEL /vmlinuz
23APPEND label=install rootwait
24
25LABEL Serial console install
26KERNEL /vmlinuz
27APPEND label=install rootwait console=ttyS0,115200
diff --git a/scripts/lib/wic/canned-wks/directdisk-bootloader-config.wks b/scripts/lib/wic/canned-wks/directdisk-bootloader-config.wks
deleted file mode 100644
index 3529e05c87..0000000000
--- a/scripts/lib/wic/canned-wks/directdisk-bootloader-config.wks
+++ /dev/null
@@ -1,8 +0,0 @@
1# short-description: Create a 'pcbios' direct disk image with custom bootloader config
2# long-description: Creates a partitioned legacy BIOS disk image that the user
3# can directly dd to boot media. The bootloader configuration source is a user file.
4
5include common.wks.inc
6
7bootloader --configfile="directdisk-bootloader-config.cfg"
8
diff --git a/scripts/lib/wic/canned-wks/directdisk-gpt.wks b/scripts/lib/wic/canned-wks/directdisk-gpt.wks
deleted file mode 100644
index 8d7d8de6ea..0000000000
--- a/scripts/lib/wic/canned-wks/directdisk-gpt.wks
+++ /dev/null
@@ -1,10 +0,0 @@
1# short-description: Create a 'pcbios' direct disk image
2# long-description: Creates a partitioned legacy BIOS disk image that the user
3# can directly dd to boot media.
4
5
6part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
7part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
8
9bootloader --ptable gpt --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"
10
diff --git a/scripts/lib/wic/canned-wks/directdisk-multi-rootfs.wks b/scripts/lib/wic/canned-wks/directdisk-multi-rootfs.wks
deleted file mode 100644
index f61d941d6d..0000000000
--- a/scripts/lib/wic/canned-wks/directdisk-multi-rootfs.wks
+++ /dev/null
@@ -1,23 +0,0 @@
1# short-description: Create multi rootfs image using rootfs plugin
2# long-description: Creates a partitioned disk image with two rootfs partitions
3# using rootfs plugin.
4#
5# Partitions can use either
6# - indirect rootfs references to image recipe(s):
7# wic create directdisk-multi-indirect-recipes -e core-image-minimal \
8# --rootfs-dir rootfs1=core-image-minimal
9# --rootfs-dir rootfs2=core-image-minimal-dev
10#
11# - or paths to rootfs directories:
12# wic create directdisk-multi-rootfs \
13# --rootfs-dir rootfs1=tmp/work/qemux86_64-poky-linux/core-image-minimal/1.0-r0/rootfs/
14# --rootfs-dir rootfs2=tmp/work/qemux86_64-poky-linux/core-image-minimal-dev/1.0-r0/rootfs/
15#
16# - or any combinations of -r and --rootfs command line options
17
18part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
19part / --source rootfs --rootfs-dir=rootfs1 --ondisk sda --fstype=ext4 --label platform --align 1024
20part /rescue --source rootfs --rootfs-dir=rootfs2 --ondisk sda --fstype=ext4 --label secondary --align 1024
21
22bootloader --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"
23
diff --git a/scripts/lib/wic/canned-wks/directdisk.wks b/scripts/lib/wic/canned-wks/directdisk.wks
deleted file mode 100644
index 8c8e06b02c..0000000000
--- a/scripts/lib/wic/canned-wks/directdisk.wks
+++ /dev/null
@@ -1,8 +0,0 @@
1# short-description: Create a 'pcbios' direct disk image
2# long-description: Creates a partitioned legacy BIOS disk image that the user
3# can directly dd to boot media.
4
5include common.wks.inc
6
7bootloader --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"
8
diff --git a/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in b/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in
deleted file mode 100644
index 2fd286ff98..0000000000
--- a/scripts/lib/wic/canned-wks/efi-bootdisk.wks.in
+++ /dev/null
@@ -1,3 +0,0 @@
1bootloader --ptable gpt
2part /boot --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --label boot --active --align 1024 --use-uuid --overhead-factor 1.1
3part / --source rootfs --fstype=ext4 --label root --align 1024 --exclude-path boot/
diff --git a/scripts/lib/wic/canned-wks/mkefidisk.wks b/scripts/lib/wic/canned-wks/mkefidisk.wks
deleted file mode 100644
index 9f534fe184..0000000000
--- a/scripts/lib/wic/canned-wks/mkefidisk.wks
+++ /dev/null
@@ -1,11 +0,0 @@
1# short-description: Create an EFI disk image
2# long-description: Creates a partitioned EFI disk image that the user
3# can directly dd to boot media.
4
5part /boot --source bootimg-efi --sourceparams="loader=grub-efi" --ondisk sda --label msdos --active --align 1024
6
7part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
8
9part swap --ondisk sda --size 44 --label swap1 --fstype=swap
10
11bootloader --ptable gpt --timeout=5 --append="rootfstype=ext4 console=ttyS0,115200 console=tty0"
diff --git a/scripts/lib/wic/canned-wks/mkhybridiso.wks b/scripts/lib/wic/canned-wks/mkhybridiso.wks
deleted file mode 100644
index 48c5ac4791..0000000000
--- a/scripts/lib/wic/canned-wks/mkhybridiso.wks
+++ /dev/null
@@ -1,7 +0,0 @@
1# short-description: Create a hybrid ISO image
2# long-description: Creates an EFI and legacy bootable hybrid ISO image
3# which can be used on optical media as well as USB media.
4
5part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi,image_name=HYBRID_ISO_IMG" --ondisk cd --label HYBRIDISO
6
7bootloader --timeout=15 --append=""
diff --git a/scripts/lib/wic/canned-wks/qemuloongarch.wks b/scripts/lib/wic/canned-wks/qemuloongarch.wks
deleted file mode 100644
index 8465c7a8c0..0000000000
--- a/scripts/lib/wic/canned-wks/qemuloongarch.wks
+++ /dev/null
@@ -1,3 +0,0 @@
1# short-description: Create qcow2 image for LoongArch QEMU machines
2
3part / --source rootfs --fstype=ext4 --label root --align 4096 --size 5G
diff --git a/scripts/lib/wic/canned-wks/qemuriscv.wks b/scripts/lib/wic/canned-wks/qemuriscv.wks
deleted file mode 100644
index 12c68b7069..0000000000
--- a/scripts/lib/wic/canned-wks/qemuriscv.wks
+++ /dev/null
@@ -1,3 +0,0 @@
1# short-description: Create qcow2 image for RISC-V QEMU machines
2
3part / --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
deleted file mode 100644
index 808997611a..0000000000
--- a/scripts/lib/wic/canned-wks/qemux86-directdisk.wks
+++ /dev/null
@@ -1,8 +0,0 @@
1# short-description: Create a qemu machine 'pcbios' direct disk image
2# long-description: Creates a partitioned legacy BIOS disk image that the user
3# can directly use to boot a qemu machine.
4
5include common.wks.inc
6
7bootloader --timeout=0 --append="rw oprofile.timer=1 rootfstype=ext4 console=tty console=ttyS0 "
8
diff --git a/scripts/lib/wic/canned-wks/sdimage-bootpart.wks b/scripts/lib/wic/canned-wks/sdimage-bootpart.wks
deleted file mode 100644
index 63bc4dab6a..0000000000
--- a/scripts/lib/wic/canned-wks/sdimage-bootpart.wks
+++ /dev/null
@@ -1,6 +0,0 @@
1# short-description: Create SD card image with a boot partition
2# long-description: Creates a partitioned SD card image. Boot files
3# are located in the first vfat partition.
4
5part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4 --size 16
6part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4
diff --git a/scripts/lib/wic/canned-wks/systemd-bootdisk.wks b/scripts/lib/wic/canned-wks/systemd-bootdisk.wks
deleted file mode 100644
index 95d7b97a60..0000000000
--- a/scripts/lib/wic/canned-wks/systemd-bootdisk.wks
+++ /dev/null
@@ -1,11 +0,0 @@
1# short-description: Create an EFI disk image with systemd-boot
2# long-description: Creates a partitioned EFI disk image that the user
3# can directly dd to boot media. The selected bootloader is systemd-boot.
4
5part /boot --source bootimg-efi --sourceparams="loader=systemd-boot" --ondisk sda --label msdos --active --align 1024 --use-uuid
6
7part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
8
9part swap --ondisk sda --size 44 --label swap1 --fstype=swap --use-uuid
10
11bootloader --ptable gpt --timeout=5 --append="rootwait rootfstype=ext4 console=ttyS0,115200 console=tty0"
diff --git a/scripts/lib/wic/engine.py b/scripts/lib/wic/engine.py
deleted file mode 100644
index 674ccfc244..0000000000
--- a/scripts/lib/wic/engine.py
+++ /dev/null
@@ -1,628 +0,0 @@
1#
2# Copyright (c) 2013, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7
8# This module implements the image creation engine used by 'wic' to
9# create images. The engine parses through the OpenEmbedded kickstart
10# (wks) file specified and generates images that can then be directly
11# written onto media.
12#
13# AUTHORS
14# Tom Zanussi <tom.zanussi (at] linux.intel.com>
15#
16
17import logging
18import os
19import tempfile
20import json
21import subprocess
22import shutil
23import re
24
25from collections import namedtuple, OrderedDict
26
27from wic import WicError
28from wic.filemap import sparse_copy
29from wic.pluginbase import PluginMgr
30from wic.misc import get_bitbake_var, exec_cmd
31
32logger = logging.getLogger('wic')
33
34def verify_build_env():
35 """
36 Verify that the build environment is sane.
37
38 Returns True if it is, false otherwise
39 """
40 if not os.environ.get("BUILDDIR"):
41 raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)")
42
43 return True
44
45
46CANNED_IMAGE_DIR = "lib/wic/canned-wks" # relative to scripts
47SCRIPTS_CANNED_IMAGE_DIR = "scripts/" + CANNED_IMAGE_DIR
48WIC_DIR = "wic"
49
50def build_canned_image_list(path):
51 layers_path = get_bitbake_var("BBLAYERS")
52 canned_wks_layer_dirs = []
53
54 if layers_path is not None:
55 for layer_path in layers_path.split():
56 for wks_path in (WIC_DIR, SCRIPTS_CANNED_IMAGE_DIR):
57 cpath = os.path.join(layer_path, wks_path)
58 if os.path.isdir(cpath):
59 canned_wks_layer_dirs.append(cpath)
60
61 cpath = os.path.join(path, CANNED_IMAGE_DIR)
62 canned_wks_layer_dirs.append(cpath)
63
64 return canned_wks_layer_dirs
65
66def find_canned_image(scripts_path, wks_file):
67 """
68 Find a .wks file with the given name in the canned files dir.
69
70 Return False if not found
71 """
72 layers_canned_wks_dir = build_canned_image_list(scripts_path)
73
74 for canned_wks_dir in layers_canned_wks_dir:
75 for root, dirs, files in os.walk(canned_wks_dir):
76 for fname in files:
77 if fname.endswith("~") or fname.endswith("#"):
78 continue
79 if ((fname.endswith(".wks") and wks_file + ".wks" == fname) or \
80 (fname.endswith(".wks.in") and wks_file + ".wks.in" == fname)):
81 fullpath = os.path.join(canned_wks_dir, fname)
82 return fullpath
83 return None
84
85
86def list_canned_images(scripts_path):
87 """
88 List the .wks files in the canned image dir, minus the extension.
89 """
90 layers_canned_wks_dir = build_canned_image_list(scripts_path)
91
92 for canned_wks_dir in layers_canned_wks_dir:
93 for root, dirs, files in os.walk(canned_wks_dir):
94 for fname in files:
95 if fname.endswith("~") or fname.endswith("#"):
96 continue
97 if fname.endswith(".wks") or fname.endswith(".wks.in"):
98 fullpath = os.path.join(canned_wks_dir, fname)
99 with open(fullpath) as wks:
100 for line in wks:
101 desc = ""
102 idx = line.find("short-description:")
103 if idx != -1:
104 desc = line[idx + len("short-description:"):].strip()
105 break
106 basename = fname.split('.')[0]
107 print(" %s\t\t%s" % (basename.ljust(30), desc))
108
109
110def list_canned_image_help(scripts_path, fullpath):
111 """
112 List the help and params in the specified canned image.
113 """
114 found = False
115 with open(fullpath) as wks:
116 for line in wks:
117 if not found:
118 idx = line.find("long-description:")
119 if idx != -1:
120 print()
121 print(line[idx + len("long-description:"):].strip())
122 found = True
123 continue
124 if not line.strip():
125 break
126 idx = line.find("#")
127 if idx != -1:
128 print(line[idx + len("#:"):].rstrip())
129 else:
130 break
131
132
133def list_source_plugins():
134 """
135 List the available source plugins i.e. plugins available for --source.
136 """
137 plugins = PluginMgr.get_plugins('source')
138
139 for plugin in plugins:
140 print(" %s" % plugin)
141
142
143def wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir,
144 native_sysroot, options):
145 """
146 Create image
147
148 wks_file - user-defined OE kickstart file
149 rootfs_dir - absolute path to the build's /rootfs dir
150 bootimg_dir - absolute path to the build's boot artifacts directory
151 kernel_dir - absolute path to the build's kernel directory
152 native_sysroot - absolute path to the build's native sysroots dir
153 image_output_dir - dirname to create for image
154 options - wic command line options (debug, bmap, etc)
155
156 Normally, the values for the build artifacts values are determined
157 by 'wic -e' from the output of the 'bitbake -e' command given an
158 image name e.g. 'core-image-minimal' and a given machine set in
159 local.conf. If that's the case, the variables get the following
160 values from the output of 'bitbake -e':
161
162 rootfs_dir: IMAGE_ROOTFS
163 kernel_dir: DEPLOY_DIR_IMAGE
164 native_sysroot: STAGING_DIR_NATIVE
165
166 In the above case, bootimg_dir remains unset and the
167 plugin-specific image creation code is responsible for finding the
168 bootimg artifacts.
169
170 In the case where the values are passed in explicitly i.e 'wic -e'
171 is not used but rather the individual 'wic' options are used to
172 explicitly specify these values.
173 """
174 try:
175 oe_builddir = os.environ["BUILDDIR"]
176 except KeyError:
177 raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)")
178
179 if not os.path.exists(options.outdir):
180 os.makedirs(options.outdir)
181
182 pname = options.imager
183 plugin_class = PluginMgr.get_plugins('imager').get(pname)
184 if not plugin_class:
185 raise WicError('Unknown plugin: %s' % pname)
186
187 plugin = plugin_class(wks_file, rootfs_dir, bootimg_dir, kernel_dir,
188 native_sysroot, oe_builddir, options)
189
190 plugin.do_create()
191
192 logger.info("The image(s) were created using OE kickstart file:\n %s", wks_file)
193
194
195def wic_list(args, scripts_path):
196 """
197 Print the list of images or source plugins.
198 """
199 if args.list_type is None:
200 return False
201
202 if args.list_type == "images":
203
204 list_canned_images(scripts_path)
205 return True
206 elif args.list_type == "source-plugins":
207 list_source_plugins()
208 return True
209 elif len(args.help_for) == 1 and args.help_for[0] == 'help':
210 wks_file = args.list_type
211 fullpath = find_canned_image(scripts_path, wks_file)
212 if not fullpath:
213 raise WicError("No image named %s found, exiting. "
214 "(Use 'wic list images' to list available images, "
215 "or specify a fully-qualified OE kickstart (.wks) "
216 "filename)" % wks_file)
217
218 list_canned_image_help(scripts_path, fullpath)
219 return True
220
221 return False
222
223
224class Disk:
225 def __init__(self, imagepath, native_sysroot, fstypes=('fat', 'ext')):
226 self.imagepath = imagepath
227 self.native_sysroot = native_sysroot
228 self.fstypes = fstypes
229 self._partitions = None
230 self._partimages = {}
231 self._lsector_size = None
232 self._psector_size = None
233 self._ptable_format = None
234
235 # find parted
236 # read paths from $PATH environment variable
237 # if it fails, use hardcoded paths
238 pathlist = "/bin:/usr/bin:/usr/sbin:/sbin/"
239 try:
240 self.paths = os.environ['PATH'] + ":" + pathlist
241 except KeyError:
242 self.paths = pathlist
243
244 if native_sysroot:
245 for path in pathlist.split(':'):
246 self.paths = "%s%s:%s" % (native_sysroot, path, self.paths)
247
248 self.parted = shutil.which("parted", path=self.paths)
249 if not self.parted:
250 raise WicError("Can't find executable parted")
251
252 self.partitions = self.get_partitions()
253
254 def __del__(self):
255 for path in self._partimages.values():
256 os.unlink(path)
257
258 def get_partitions(self):
259 if self._partitions is None:
260 self._partitions = OrderedDict()
261 out = exec_cmd("%s -sm %s unit B print" % (self.parted, self.imagepath))
262 parttype = namedtuple("Part", "pnum start end size fstype")
263 splitted = out.splitlines()
264 # skip over possible errors in exec_cmd output
265 try:
266 idx =splitted.index("BYT;")
267 except ValueError:
268 raise WicError("Error getting partition information from %s" % (self.parted))
269 lsector_size, psector_size, self._ptable_format = splitted[idx + 1].split(":")[3:6]
270 self._lsector_size = int(lsector_size)
271 self._psector_size = int(psector_size)
272 for line in splitted[idx + 2:]:
273 pnum, start, end, size, fstype = line.split(':')[:5]
274 partition = parttype(int(pnum), int(start[:-1]), int(end[:-1]),
275 int(size[:-1]), fstype)
276 self._partitions[pnum] = partition
277
278 return self._partitions
279
280 def __getattr__(self, name):
281 """Get path to the executable in a lazy way."""
282 if name in ("mdir", "mcopy", "mdel", "mdeltree", "sfdisk", "e2fsck",
283 "resize2fs", "mkswap", "mkdosfs", "debugfs","blkid"):
284 aname = "_%s" % name
285 if aname not in self.__dict__:
286 setattr(self, aname, shutil.which(name, path=self.paths))
287 if aname not in self.__dict__ or self.__dict__[aname] is None:
288 raise WicError("Can't find executable '{}'".format(name))
289 return self.__dict__[aname]
290 return self.__dict__[name]
291
292 def _get_part_image(self, pnum):
293 if pnum not in self.partitions:
294 raise WicError("Partition %s is not in the image" % pnum)
295 part = self.partitions[pnum]
296 # check if fstype is supported
297 for fstype in self.fstypes:
298 if part.fstype.startswith(fstype):
299 break
300 else:
301 raise WicError("Not supported fstype: {}".format(part.fstype))
302 if pnum not in self._partimages:
303 tmpf = tempfile.NamedTemporaryFile(prefix="wic-part")
304 dst_fname = tmpf.name
305 tmpf.close()
306 sparse_copy(self.imagepath, dst_fname, skip=part.start, length=part.size)
307 self._partimages[pnum] = dst_fname
308
309 return self._partimages[pnum]
310
311 def _put_part_image(self, pnum):
312 """Put partition image into partitioned image."""
313 sparse_copy(self._partimages[pnum], self.imagepath,
314 seek=self.partitions[pnum].start)
315
316 def dir(self, pnum, path):
317 if pnum not in self.partitions:
318 raise WicError("Partition %s is not in the image" % pnum)
319
320 if self.partitions[pnum].fstype.startswith('ext'):
321 return exec_cmd("{} {} -R 'ls -l {}'".format(self.debugfs,
322 self._get_part_image(pnum),
323 path), as_shell=True)
324 else: # fat
325 return exec_cmd("{} -i {} ::{}".format(self.mdir,
326 self._get_part_image(pnum),
327 path))
328
329 def copy(self, src, dest):
330 """Copy partition image into wic image."""
331 pnum = dest.part if isinstance(src, str) else src.part
332
333 if self.partitions[pnum].fstype.startswith('ext'):
334 if isinstance(src, str):
335 cmd = "printf 'cd {}\nwrite {} {}\n' | {} -w {}".\
336 format(os.path.dirname(dest.path), src, os.path.basename(src),
337 self.debugfs, self._get_part_image(pnum))
338 else: # copy from wic
339 # run both dump and rdump to support both files and directory
340 cmd = "printf 'cd {}\ndump /{} {}\nrdump /{} {}\n' | {} {}".\
341 format(os.path.dirname(src.path), src.path,
342 dest, src.path, dest, self.debugfs,
343 self._get_part_image(pnum))
344 else: # fat
345 if isinstance(src, str):
346 cmd = "{} -i {} -snop {} ::{}".format(self.mcopy,
347 self._get_part_image(pnum),
348 src, dest.path)
349 else:
350 cmd = "{} -i {} -snop ::{} {}".format(self.mcopy,
351 self._get_part_image(pnum),
352 src.path, dest)
353
354 exec_cmd(cmd, as_shell=True)
355 self._put_part_image(pnum)
356
357 def remove_ext(self, pnum, path, recursive):
358 """
359 Remove files/dirs and their contents from the partition.
360 This only applies to ext* partition.
361 """
362 abs_path = re.sub('\/\/+', '/', path)
363 cmd = "{} {} -wR 'rm \"{}\"'".format(self.debugfs,
364 self._get_part_image(pnum),
365 abs_path)
366 out = exec_cmd(cmd , as_shell=True)
367 for line in out.splitlines():
368 if line.startswith("rm:"):
369 if "file is a directory" in line:
370 if recursive:
371 # loop through content and delete them one by one if
372 # flaged with -r
373 subdirs = iter(self.dir(pnum, abs_path).splitlines())
374 next(subdirs)
375 for subdir in subdirs:
376 dir = subdir.split(':')[1].split(" ", 1)[1]
377 if not dir == "." and not dir == "..":
378 self.remove_ext(pnum, "%s/%s" % (abs_path, dir), recursive)
379
380 rmdir_out = exec_cmd("{} {} -wR 'rmdir \"{}\"'".format(self.debugfs,
381 self._get_part_image(pnum),
382 abs_path.rstrip('/'))
383 , as_shell=True)
384
385 for rmdir_line in rmdir_out.splitlines():
386 if "directory not empty" in rmdir_line:
387 raise WicError("Could not complete operation: \n%s \n"
388 "use -r to remove non-empty directory" % rmdir_line)
389 if rmdir_line.startswith("rmdir:"):
390 raise WicError("Could not complete operation: \n%s "
391 "\n%s" % (str(line), rmdir_line))
392
393 else:
394 raise WicError("Could not complete operation: \n%s "
395 "\nUnable to remove %s" % (str(line), abs_path))
396
397 def remove(self, pnum, path, recursive):
398 """Remove files/dirs from the partition."""
399 partimg = self._get_part_image(pnum)
400 if self.partitions[pnum].fstype.startswith('ext'):
401 self.remove_ext(pnum, path, recursive)
402
403 else: # fat
404 cmd = "{} -i {} ::{}".format(self.mdel, partimg, path)
405 try:
406 exec_cmd(cmd)
407 except WicError as err:
408 if "not found" in str(err) or "non empty" in str(err):
409 # mdel outputs 'File ... not found' or 'directory .. non empty"
410 # try to use mdeltree as path could be a directory
411 cmd = "{} -i {} ::{}".format(self.mdeltree,
412 partimg, path)
413 exec_cmd(cmd)
414 else:
415 raise err
416 self._put_part_image(pnum)
417
418 def write(self, target, expand):
419 """Write disk image to the media or file."""
420 def write_sfdisk_script(outf, parts):
421 for key, val in parts['partitiontable'].items():
422 if key in ("partitions", "device", "firstlba", "lastlba"):
423 continue
424 if key == "id":
425 key = "label-id"
426 outf.write("{}: {}\n".format(key, val))
427 outf.write("\n")
428 for part in parts['partitiontable']['partitions']:
429 line = ''
430 for name in ('attrs', 'name', 'size', 'type', 'uuid'):
431 if name == 'size' and part['type'] == 'f':
432 # don't write size for extended partition
433 continue
434 val = part.get(name)
435 if val:
436 line += '{}={}, '.format(name, val)
437 if line:
438 line = line[:-2] # strip ', '
439 if part.get('bootable'):
440 line += ' ,bootable'
441 outf.write("{}\n".format(line))
442 outf.flush()
443
444 def read_ptable(path):
445 out = exec_cmd("{} -J {}".format(self.sfdisk, path))
446 return json.loads(out)
447
448 def write_ptable(parts, target):
449 with tempfile.NamedTemporaryFile(prefix="wic-sfdisk-", mode='w') as outf:
450 write_sfdisk_script(outf, parts)
451 cmd = "{} --no-reread {} < {} ".format(self.sfdisk, target, outf.name)
452 exec_cmd(cmd, as_shell=True)
453
454 if expand is None:
455 sparse_copy(self.imagepath, target)
456 else:
457 # copy first sectors that may contain bootloader
458 sparse_copy(self.imagepath, target, length=2048 * self._lsector_size)
459
460 # copy source partition table to the target
461 parts = read_ptable(self.imagepath)
462 write_ptable(parts, target)
463
464 # get size of unpartitioned space
465 free = None
466 for line in exec_cmd("{} -F {}".format(self.sfdisk, target)).splitlines():
467 if line.startswith("Unpartitioned space ") and line.endswith("sectors"):
468 free = int(line.split()[-2])
469 # Align free space to a 2048 sector boundary. YOCTO #12840.
470 free = free - (free % 2048)
471 if free is None:
472 raise WicError("Can't get size of unpartitioned space")
473
474 # calculate expanded partitions sizes
475 sizes = {}
476 num_auto_resize = 0
477 for num, part in enumerate(parts['partitiontable']['partitions'], 1):
478 if num in expand:
479 if expand[num] != 0: # don't resize partition if size is set to 0
480 sectors = expand[num] // self._lsector_size
481 free -= sectors - part['size']
482 part['size'] = sectors
483 sizes[num] = sectors
484 elif part['type'] != 'f':
485 sizes[num] = -1
486 num_auto_resize += 1
487
488 for num, part in enumerate(parts['partitiontable']['partitions'], 1):
489 if sizes.get(num) == -1:
490 part['size'] += free // num_auto_resize
491
492 # write resized partition table to the target
493 write_ptable(parts, target)
494
495 # read resized partition table
496 parts = read_ptable(target)
497
498 # copy partitions content
499 for num, part in enumerate(parts['partitiontable']['partitions'], 1):
500 pnum = str(num)
501 fstype = self.partitions[pnum].fstype
502
503 # copy unchanged partition
504 if part['size'] == self.partitions[pnum].size // self._lsector_size:
505 logger.info("copying unchanged partition {}".format(pnum))
506 sparse_copy(self._get_part_image(pnum), target, seek=part['start'] * self._lsector_size)
507 continue
508
509 # resize or re-create partitions
510 if fstype.startswith('ext') or fstype.startswith('fat') or \
511 fstype.startswith('linux-swap'):
512
513 partfname = None
514 with tempfile.NamedTemporaryFile(prefix="wic-part{}-".format(pnum)) as partf:
515 partfname = partf.name
516
517 if fstype.startswith('ext'):
518 logger.info("resizing ext partition {}".format(pnum))
519 partimg = self._get_part_image(pnum)
520 sparse_copy(partimg, partfname)
521 exec_cmd("{} -pf {}".format(self.e2fsck, partfname))
522 exec_cmd("{} {} {}s".format(\
523 self.resize2fs, partfname, part['size']))
524 elif fstype.startswith('fat'):
525 logger.info("copying content of the fat partition {}".format(pnum))
526 with tempfile.TemporaryDirectory(prefix='wic-fatdir-') as tmpdir:
527 # copy content to the temporary directory
528 cmd = "{} -snompi {} :: {}".format(self.mcopy,
529 self._get_part_image(pnum),
530 tmpdir)
531 exec_cmd(cmd)
532 # create new msdos partition
533 label = part.get("name")
534 label_str = "-n {}".format(label) if label else ''
535
536 cmd = "{} {} -C {} {}".format(self.mkdosfs, label_str, partfname,
537 part['size'])
538 exec_cmd(cmd)
539 # copy content from the temporary directory to the new partition
540 cmd = "{} -snompi {} {}/* ::".format(self.mcopy, partfname, tmpdir)
541 exec_cmd(cmd, as_shell=True)
542 elif fstype.startswith('linux-swap'):
543 logger.info("creating swap partition {}".format(pnum))
544 label = part.get("name")
545 label_str = "-L {}".format(label) if label else ''
546 out = exec_cmd("{} --probe {}".format(self.blkid, self._get_part_image(pnum)))
547 uuid = out[out.index("UUID=\"")+6:out.index("UUID=\"")+42]
548 uuid_str = "-U {}".format(uuid) if uuid else ''
549 with open(partfname, 'w') as sparse:
550 os.ftruncate(sparse.fileno(), part['size'] * self._lsector_size)
551 exec_cmd("{} {} {} {}".format(self.mkswap, label_str, uuid_str, partfname))
552 sparse_copy(partfname, target, seek=part['start'] * self._lsector_size)
553 os.unlink(partfname)
554 elif part['type'] != 'f':
555 logger.warning("skipping partition {}: unsupported fstype {}".format(pnum, fstype))
556
557def wic_ls(args, native_sysroot):
558 """List contents of partitioned image or vfat partition."""
559 disk = Disk(args.path.image, native_sysroot)
560 if not args.path.part:
561 if disk.partitions:
562 print('Num Start End Size Fstype')
563 for part in disk.partitions.values():
564 print("{:2d} {:12d} {:12d} {:12d} {}".format(\
565 part.pnum, part.start, part.end,
566 part.size, part.fstype))
567 else:
568 path = args.path.path or '/'
569 print(disk.dir(args.path.part, path))
570
571def wic_cp(args, native_sysroot):
572 """
573 Copy file or directory to/from the vfat/ext partition of
574 partitioned image.
575 """
576 if isinstance(args.dest, str):
577 disk = Disk(args.src.image, native_sysroot)
578 else:
579 disk = Disk(args.dest.image, native_sysroot)
580 disk.copy(args.src, args.dest)
581
582
583def wic_rm(args, native_sysroot):
584 """
585 Remove files or directories from the vfat partition of
586 partitioned image.
587 """
588 disk = Disk(args.path.image, native_sysroot)
589 disk.remove(args.path.part, args.path.path, args.recursive_delete)
590
591def wic_write(args, native_sysroot):
592 """
593 Write image to a target device.
594 """
595 disk = Disk(args.image, native_sysroot, ('fat', 'ext', 'linux-swap'))
596 disk.write(args.target, args.expand)
597
598def find_canned(scripts_path, file_name):
599 """
600 Find a file either by its path or by name in the canned files dir.
601
602 Return None if not found
603 """
604 if os.path.exists(file_name):
605 return file_name
606
607 layers_canned_wks_dir = build_canned_image_list(scripts_path)
608 for canned_wks_dir in layers_canned_wks_dir:
609 for root, dirs, files in os.walk(canned_wks_dir):
610 for fname in files:
611 if fname == file_name:
612 fullpath = os.path.join(canned_wks_dir, fname)
613 return fullpath
614
615def get_custom_config(boot_file):
616 """
617 Get the custom configuration to be used for the bootloader.
618
619 Return None if the file can't be found.
620 """
621 # Get the scripts path of poky
622 scripts_path = os.path.abspath("%s/../.." % os.path.dirname(__file__))
623
624 cfg_file = find_canned(scripts_path, boot_file)
625 if cfg_file:
626 with open(cfg_file, "r") as f:
627 config = f.read()
628 return config
diff --git a/scripts/lib/wic/filemap.py b/scripts/lib/wic/filemap.py
deleted file mode 100644
index 85b39d5d74..0000000000
--- a/scripts/lib/wic/filemap.py
+++ /dev/null
@@ -1,583 +0,0 @@
1#
2# Copyright (c) 2012 Intel, Inc.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7"""
8This module implements python implements a way to get file block. Two methods
9are supported - the FIEMAP ioctl and the 'SEEK_HOLE / SEEK_DATA' features of
10the file seek syscall. The former is implemented by the 'FilemapFiemap' class,
11the latter is implemented by the 'FilemapSeek' class. Both classes provide the
12same API. The 'filemap' function automatically selects which class can be used
13and returns an instance of the class.
14"""
15
16# Disable the following pylint recommendations:
17# * Too many instance attributes (R0902)
18# pylint: disable=R0902
19
20import errno
21import os
22import struct
23import array
24import fcntl
25import tempfile
26import logging
27
28def get_block_size(file_obj):
29 """
30 Returns block size for file object 'file_obj'. Errors are indicated by the
31 'IOError' exception.
32 """
33 # Get the block size of the host file-system for the image file by calling
34 # the FIGETBSZ ioctl (number 2).
35 try:
36 binary_data = fcntl.ioctl(file_obj, 2, struct.pack('I', 0))
37 bsize = struct.unpack('I', binary_data)[0]
38 except OSError:
39 bsize = None
40
41 # If ioctl causes OSError or give bsize to zero failback to os.fstat
42 if not bsize:
43 import os
44 stat = os.fstat(file_obj.fileno())
45 if hasattr(stat, 'st_blksize'):
46 bsize = stat.st_blksize
47 else:
48 raise IOError("Unable to determine block size")
49
50 # The logic in this script only supports a maximum of a 4KB
51 # block size
52 max_block_size = 4 * 1024
53 if bsize > max_block_size:
54 bsize = max_block_size
55
56 return bsize
57
58class ErrorNotSupp(Exception):
59 """
60 An exception of this type is raised when the 'FIEMAP' or 'SEEK_HOLE' feature
61 is not supported either by the kernel or the file-system.
62 """
63 pass
64
65class Error(Exception):
66 """A class for all the other exceptions raised by this module."""
67 pass
68
69
70class _FilemapBase(object):
71 """
72 This is a base class for a couple of other classes in this module. This
73 class simply performs the common parts of the initialization process: opens
74 the image file, gets its size, etc. The 'log' parameter is the logger object
75 to use for printing messages.
76 """
77
78 def __init__(self, image, log=None):
79 """
80 Initialize a class instance. The 'image' argument is full path to the
81 file or file object to operate on.
82 """
83
84 self._log = log
85 if self._log is None:
86 self._log = logging.getLogger(__name__)
87
88 self._f_image_needs_close = False
89
90 if hasattr(image, "fileno"):
91 self._f_image = image
92 self._image_path = image.name
93 else:
94 self._image_path = image
95 self._open_image_file()
96
97 try:
98 self.image_size = os.fstat(self._f_image.fileno()).st_size
99 except IOError as err:
100 raise Error("cannot get information about file '%s': %s"
101 % (self._f_image.name, err))
102
103 try:
104 self.block_size = get_block_size(self._f_image)
105 except IOError as err:
106 raise Error("cannot get block size for '%s': %s"
107 % (self._image_path, err))
108
109 self.blocks_cnt = self.image_size + self.block_size - 1
110 self.blocks_cnt //= self.block_size
111
112 try:
113 self._f_image.flush()
114 except IOError as err:
115 raise Error("cannot flush image file '%s': %s"
116 % (self._image_path, err))
117
118 try:
119 os.fsync(self._f_image.fileno()),
120 except OSError as err:
121 raise Error("cannot synchronize image file '%s': %s "
122 % (self._image_path, err.strerror))
123
124 self._log.debug("opened image \"%s\"" % self._image_path)
125 self._log.debug("block size %d, blocks count %d, image size %d"
126 % (self.block_size, self.blocks_cnt, self.image_size))
127
128 def __del__(self):
129 """The class destructor which just closes the image file."""
130 if self._f_image_needs_close:
131 self._f_image.close()
132
133 def _open_image_file(self):
134 """Open the image file."""
135 try:
136 self._f_image = open(self._image_path, 'rb')
137 except IOError as err:
138 raise Error("cannot open image file '%s': %s"
139 % (self._image_path, err))
140
141 self._f_image_needs_close = True
142
143 def block_is_mapped(self, block): # pylint: disable=W0613,R0201
144 """
145 This method has has to be implemented by child classes. It returns
146 'True' if block number 'block' of the image file is mapped and 'False'
147 otherwise.
148 """
149
150 raise Error("the method is not implemented")
151
152 def get_mapped_ranges(self, start, count): # pylint: disable=W0613,R0201
153 """
154 This method has has to be implemented by child classes. This is a
155 generator which yields ranges of mapped blocks in the file. The ranges
156 are tuples of 2 elements: [first, last], where 'first' is the first
157 mapped block and 'last' is the last mapped block.
158
159 The ranges are yielded for the area of the file of size 'count' blocks,
160 starting from block 'start'.
161 """
162
163 raise Error("the method is not implemented")
164
165
166# The 'SEEK_HOLE' and 'SEEK_DATA' options of the file seek system call
167_SEEK_DATA = 3
168_SEEK_HOLE = 4
169
170def _lseek(file_obj, offset, whence):
171 """This is a helper function which invokes 'os.lseek' for file object
172 'file_obj' and with specified 'offset' and 'whence'. The 'whence'
173 argument is supposed to be either '_SEEK_DATA' or '_SEEK_HOLE'. When
174 there is no more data or hole starting from 'offset', this function
175 returns '-1'. Otherwise the data or hole position is returned."""
176
177 try:
178 return os.lseek(file_obj.fileno(), offset, whence)
179 except OSError as err:
180 # The 'lseek' system call returns the ENXIO if there is no data or
181 # hole starting from the specified offset.
182 if err.errno == errno.ENXIO:
183 return -1
184 elif err.errno == errno.EINVAL:
185 raise ErrorNotSupp("the kernel or file-system does not support "
186 "\"SEEK_HOLE\" and \"SEEK_DATA\"")
187 else:
188 raise
189
190class FilemapSeek(_FilemapBase):
191 """
192 This class uses the 'SEEK_HOLE' and 'SEEK_DATA' to find file block mapping.
193 Unfortunately, the current implementation requires the caller to have write
194 access to the image file.
195 """
196
197 def __init__(self, image, log=None):
198 """Refer the '_FilemapBase' class for the documentation."""
199
200 # Call the base class constructor first
201 _FilemapBase.__init__(self, image, log)
202 self._log.debug("FilemapSeek: initializing")
203
204 self._probe_seek_hole()
205
206 def _probe_seek_hole(self):
207 """
208 Check whether the system implements 'SEEK_HOLE' and 'SEEK_DATA'.
209 Unfortunately, there seems to be no clean way for detecting this,
210 because often the system just fakes them by just assuming that all
211 files are fully mapped, so 'SEEK_HOLE' always returns EOF and
212 'SEEK_DATA' always returns the requested offset.
213
214 I could not invent a better way of detecting the fake 'SEEK_HOLE'
215 implementation than just to create a temporary file in the same
216 directory where the image file resides. It would be nice to change this
217 to something better.
218 """
219
220 directory = os.path.dirname(self._image_path)
221
222 try:
223 tmp_obj = tempfile.TemporaryFile("w+", dir=directory)
224 except IOError as err:
225 raise ErrorNotSupp("cannot create a temporary in \"%s\": %s" \
226 % (directory, err))
227
228 try:
229 os.ftruncate(tmp_obj.fileno(), self.block_size)
230 except OSError as err:
231 raise ErrorNotSupp("cannot truncate temporary file in \"%s\": %s"
232 % (directory, err))
233
234 offs = _lseek(tmp_obj, 0, _SEEK_HOLE)
235 if offs != 0:
236 # We are dealing with the stub 'SEEK_HOLE' implementation which
237 # always returns EOF.
238 self._log.debug("lseek(0, SEEK_HOLE) returned %d" % offs)
239 raise ErrorNotSupp("the file-system does not support "
240 "\"SEEK_HOLE\" and \"SEEK_DATA\" but only "
241 "provides a stub implementation")
242
243 tmp_obj.close()
244
245 def block_is_mapped(self, block):
246 """Refer the '_FilemapBase' class for the documentation."""
247 offs = _lseek(self._f_image, block * self.block_size, _SEEK_DATA)
248 if offs == -1:
249 result = False
250 else:
251 result = (offs // self.block_size == block)
252
253 self._log.debug("FilemapSeek: block_is_mapped(%d) returns %s"
254 % (block, result))
255 return result
256
257 def _get_ranges(self, start, count, whence1, whence2):
258 """
259 This function implements 'get_mapped_ranges()' depending
260 on what is passed in the 'whence1' and 'whence2' arguments.
261 """
262
263 assert whence1 != whence2
264 end = start * self.block_size
265 limit = end + count * self.block_size
266
267 while True:
268 start = _lseek(self._f_image, end, whence1)
269 if start == -1 or start >= limit or start == self.image_size:
270 break
271
272 end = _lseek(self._f_image, start, whence2)
273 if end == -1 or end == self.image_size:
274 end = self.blocks_cnt * self.block_size
275 if end > limit:
276 end = limit
277
278 start_blk = start // self.block_size
279 end_blk = end // self.block_size - 1
280 self._log.debug("FilemapSeek: yielding range (%d, %d)"
281 % (start_blk, end_blk))
282 yield (start_blk, end_blk)
283
284 def get_mapped_ranges(self, start, count):
285 """Refer the '_FilemapBase' class for the documentation."""
286 self._log.debug("FilemapSeek: get_mapped_ranges(%d, %d(%d))"
287 % (start, count, start + count - 1))
288 return self._get_ranges(start, count, _SEEK_DATA, _SEEK_HOLE)
289
290
291# Below goes the FIEMAP ioctl implementation, which is not very readable
292# because it deals with the rather complex FIEMAP ioctl. To understand the
293# code, you need to know the FIEMAP interface, which is documented in the
294# "Documentation/filesystems/fiemap.txt" file in the Linux kernel sources.
295
296# Format string for 'struct fiemap'
297_FIEMAP_FORMAT = "=QQLLLL"
298# sizeof(struct fiemap)
299_FIEMAP_SIZE = struct.calcsize(_FIEMAP_FORMAT)
300# Format string for 'struct fiemap_extent'
301_FIEMAP_EXTENT_FORMAT = "=QQQQQLLLL"
302# sizeof(struct fiemap_extent)
303_FIEMAP_EXTENT_SIZE = struct.calcsize(_FIEMAP_EXTENT_FORMAT)
304# The FIEMAP ioctl number
305_FIEMAP_IOCTL = 0xC020660B
306# This FIEMAP ioctl flag which instructs the kernel to sync the file before
307# reading the block map
308_FIEMAP_FLAG_SYNC = 0x00000001
309# Size of the buffer for 'struct fiemap_extent' elements which will be used
310# when invoking the FIEMAP ioctl. The larger is the buffer, the less times the
311# FIEMAP ioctl will be invoked.
312_FIEMAP_BUFFER_SIZE = 256 * 1024
313
314class FilemapFiemap(_FilemapBase):
315 """
316 This class provides API to the FIEMAP ioctl. Namely, it allows to iterate
317 over all mapped blocks and over all holes.
318
319 This class synchronizes the image file every time it invokes the FIEMAP
320 ioctl in order to work-around early FIEMAP implementation kernel bugs.
321 """
322
323 def __init__(self, image, log=None):
324 """
325 Initialize a class instance. The 'image' argument is full the file
326 object to operate on.
327 """
328
329 # Call the base class constructor first
330 _FilemapBase.__init__(self, image, log)
331 self._log.debug("FilemapFiemap: initializing")
332
333 self._buf_size = _FIEMAP_BUFFER_SIZE
334
335 # Calculate how many 'struct fiemap_extent' elements fit the buffer
336 self._buf_size -= _FIEMAP_SIZE
337 self._fiemap_extent_cnt = self._buf_size // _FIEMAP_EXTENT_SIZE
338 assert self._fiemap_extent_cnt > 0
339 self._buf_size = self._fiemap_extent_cnt * _FIEMAP_EXTENT_SIZE
340 self._buf_size += _FIEMAP_SIZE
341
342 # Allocate a mutable buffer for the FIEMAP ioctl
343 self._buf = array.array('B', [0] * self._buf_size)
344
345 # Check if the FIEMAP ioctl is supported
346 self.block_is_mapped(0)
347
348 def _invoke_fiemap(self, block, count):
349 """
350 Invoke the FIEMAP ioctl for 'count' blocks of the file starting from
351 block number 'block'.
352
353 The full result of the operation is stored in 'self._buf' on exit.
354 Returns the unpacked 'struct fiemap' data structure in form of a python
355 list (just like 'struct.upack()').
356 """
357
358 if self.blocks_cnt != 0 and (block < 0 or block >= self.blocks_cnt):
359 raise Error("bad block number %d, should be within [0, %d]"
360 % (block, self.blocks_cnt))
361
362 # Initialize the 'struct fiemap' part of the buffer. We use the
363 # '_FIEMAP_FLAG_SYNC' flag in order to make sure the file is
364 # synchronized. The reason for this is that early FIEMAP
365 # implementations had many bugs related to cached dirty data, and
366 # synchronizing the file is a necessary work-around.
367 struct.pack_into(_FIEMAP_FORMAT, self._buf, 0, block * self.block_size,
368 count * self.block_size, _FIEMAP_FLAG_SYNC, 0,
369 self._fiemap_extent_cnt, 0)
370
371 try:
372 fcntl.ioctl(self._f_image, _FIEMAP_IOCTL, self._buf, 1)
373 except IOError as err:
374 # Note, the FIEMAP ioctl is supported by the Linux kernel starting
375 # from version 2.6.28 (year 2008).
376 if err.errno == errno.EOPNOTSUPP:
377 errstr = "FilemapFiemap: the FIEMAP ioctl is not supported " \
378 "by the file-system"
379 self._log.debug(errstr)
380 raise ErrorNotSupp(errstr)
381 if err.errno == errno.ENOTTY:
382 errstr = "FilemapFiemap: the FIEMAP ioctl is not supported " \
383 "by the kernel"
384 self._log.debug(errstr)
385 raise ErrorNotSupp(errstr)
386 raise Error("the FIEMAP ioctl failed for '%s': %s"
387 % (self._image_path, err))
388
389 return struct.unpack(_FIEMAP_FORMAT, self._buf[:_FIEMAP_SIZE])
390
391 def block_is_mapped(self, block):
392 """Refer the '_FilemapBase' class for the documentation."""
393 struct_fiemap = self._invoke_fiemap(block, 1)
394
395 # The 3rd element of 'struct_fiemap' is the 'fm_mapped_extents' field.
396 # If it contains zero, the block is not mapped, otherwise it is
397 # mapped.
398 result = bool(struct_fiemap[3])
399 self._log.debug("FilemapFiemap: block_is_mapped(%d) returns %s"
400 % (block, result))
401 return result
402
403 def _unpack_fiemap_extent(self, index):
404 """
405 Unpack a 'struct fiemap_extent' structure object number 'index' from
406 the internal 'self._buf' buffer.
407 """
408
409 offset = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE * index
410 return struct.unpack(_FIEMAP_EXTENT_FORMAT,
411 self._buf[offset : offset + _FIEMAP_EXTENT_SIZE])
412
413 def _do_get_mapped_ranges(self, start, count):
414 """
415 Implements most the functionality for the 'get_mapped_ranges()'
416 generator: invokes the FIEMAP ioctl, walks through the mapped extents
417 and yields mapped block ranges. However, the ranges may be consecutive
418 (e.g., (1, 100), (100, 200)) and 'get_mapped_ranges()' simply merges
419 them.
420 """
421
422 block = start
423 while block < start + count:
424 struct_fiemap = self._invoke_fiemap(block, count)
425
426 mapped_extents = struct_fiemap[3]
427 if mapped_extents == 0:
428 # No more mapped blocks
429 return
430
431 extent = 0
432 while extent < mapped_extents:
433 fiemap_extent = self._unpack_fiemap_extent(extent)
434
435 # Start of the extent
436 extent_start = fiemap_extent[0]
437 # Starting block number of the extent
438 extent_block = extent_start // self.block_size
439 # Length of the extent
440 extent_len = fiemap_extent[2]
441 # Count of blocks in the extent
442 extent_count = extent_len // self.block_size
443
444 # Extent length and offset have to be block-aligned
445 assert extent_start % self.block_size == 0
446 assert extent_len % self.block_size == 0
447
448 if extent_block > start + count - 1:
449 return
450
451 first = max(extent_block, block)
452 last = min(extent_block + extent_count, start + count) - 1
453 yield (first, last)
454
455 extent += 1
456
457 block = extent_block + extent_count
458
459 def get_mapped_ranges(self, start, count):
460 """Refer the '_FilemapBase' class for the documentation."""
461 self._log.debug("FilemapFiemap: get_mapped_ranges(%d, %d(%d))"
462 % (start, count, start + count - 1))
463 iterator = self._do_get_mapped_ranges(start, count)
464 first_prev, last_prev = next(iterator)
465
466 for first, last in iterator:
467 if last_prev == first - 1:
468 last_prev = last
469 else:
470 self._log.debug("FilemapFiemap: yielding range (%d, %d)"
471 % (first_prev, last_prev))
472 yield (first_prev, last_prev)
473 first_prev, last_prev = first, last
474
475 self._log.debug("FilemapFiemap: yielding range (%d, %d)"
476 % (first_prev, last_prev))
477 yield (first_prev, last_prev)
478
479class FilemapNobmap(_FilemapBase):
480 """
481 This class is used when both the 'SEEK_DATA/HOLE' and FIEMAP are not
482 supported by the filesystem or kernel.
483 """
484
485 def __init__(self, image, log=None):
486 """Refer the '_FilemapBase' class for the documentation."""
487
488 # Call the base class constructor first
489 _FilemapBase.__init__(self, image, log)
490 self._log.debug("FilemapNobmap: initializing")
491
492 def block_is_mapped(self, block):
493 """Refer the '_FilemapBase' class for the documentation."""
494 return True
495
496 def get_mapped_ranges(self, start, count):
497 """Refer the '_FilemapBase' class for the documentation."""
498 self._log.debug("FilemapNobmap: get_mapped_ranges(%d, %d(%d))"
499 % (start, count, start + count - 1))
500 yield (start, start + count -1)
501
502def filemap(image, log=None):
503 """
504 Create and return an instance of a Filemap class - 'FilemapFiemap' or
505 'FilemapSeek', depending on what the system we run on supports. If the
506 FIEMAP ioctl is supported, an instance of the 'FilemapFiemap' class is
507 returned. Otherwise, if 'SEEK_HOLE' is supported an instance of the
508 'FilemapSeek' class is returned. If none of these are supported, the
509 function generates an 'Error' type exception.
510 """
511
512 try:
513 return FilemapFiemap(image, log)
514 except ErrorNotSupp:
515 try:
516 return FilemapSeek(image, log)
517 except ErrorNotSupp:
518 return FilemapNobmap(image, log)
519
520def sparse_copy(src_fname, dst_fname, skip=0, seek=0,
521 length=0, api=None):
522 """
523 Efficiently copy sparse file to or into another file.
524
525 src_fname: path to source file
526 dst_fname: path to destination file
527 skip: skip N bytes at thestart of src
528 seek: seek N bytes from the start of dst
529 length: read N bytes from src and write them to dst
530 api: FilemapFiemap or FilemapSeek object
531 """
532 if not api:
533 api = filemap
534 fmap = api(src_fname)
535 try:
536 dst_file = open(dst_fname, 'r+b')
537 except IOError:
538 dst_file = open(dst_fname, 'wb')
539 if length:
540 dst_size = length + seek
541 else:
542 dst_size = os.path.getsize(src_fname) + seek - skip
543 dst_file.truncate(dst_size)
544
545 written = 0
546 for first, last in fmap.get_mapped_ranges(0, fmap.blocks_cnt):
547 start = first * fmap.block_size
548 end = (last + 1) * fmap.block_size
549
550 if skip >= end:
551 continue
552
553 if start < skip < end:
554 start = skip
555
556 fmap._f_image.seek(start, os.SEEK_SET)
557
558 written += start - skip - written
559 if length and written >= length:
560 dst_file.seek(seek + length, os.SEEK_SET)
561 dst_file.close()
562 return
563
564 dst_file.seek(seek + start - skip, os.SEEK_SET)
565
566 chunk_size = 1024 * 1024
567 to_read = end - start
568 read = 0
569
570 while read < to_read:
571 if read + chunk_size > to_read:
572 chunk_size = to_read - read
573 size = chunk_size
574 if length and written + size > length:
575 size = length - written
576 chunk = fmap._f_image.read(size)
577 dst_file.write(chunk)
578 read += size
579 written += size
580 if written == length:
581 dst_file.close()
582 return
583 dst_file.close()
diff --git a/scripts/lib/wic/help.py b/scripts/lib/wic/help.py
deleted file mode 100644
index 163535e431..0000000000
--- a/scripts/lib/wic/help.py
+++ /dev/null
@@ -1,1148 +0,0 @@
1# Copyright (c) 2013, Intel Corporation.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5# DESCRIPTION
6# This module implements some basic help invocation functions along
7# with the bulk of the help topic text for the OE Core Image Tools.
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11#
12
13import subprocess
14import logging
15
16from wic.pluginbase import PluginMgr, PLUGIN_TYPES
17
18logger = logging.getLogger('wic')
19
20def subcommand_error(args):
21 logger.info("invalid subcommand %s", args[0])
22
23
24def display_help(subcommand, subcommands):
25 """
26 Display help for subcommand.
27 """
28 if subcommand not in subcommands:
29 return False
30
31 hlp = subcommands.get(subcommand, subcommand_error)[2]
32 if callable(hlp):
33 hlp = hlp()
34 pager = subprocess.Popen('less', stdin=subprocess.PIPE)
35 pager.communicate(hlp.encode('utf-8'))
36
37 return True
38
39
40def wic_help(args, usage_str, subcommands):
41 """
42 Subcommand help dispatcher.
43 """
44 if args.help_topic == None or not display_help(args.help_topic, subcommands):
45 print(usage_str)
46
47
48def get_wic_plugins_help():
49 """
50 Combine wic_plugins_help with the help for every known
51 source plugin.
52 """
53 result = wic_plugins_help
54 for plugin_type in PLUGIN_TYPES:
55 result += '\n\n%s PLUGINS\n\n' % plugin_type.upper()
56 for name, plugin in PluginMgr.get_plugins(plugin_type).items():
57 result += "\n %s plugin:\n" % name
58 if plugin.__doc__:
59 result += plugin.__doc__
60 else:
61 result += "\n %s is missing docstring\n" % plugin
62 return result
63
64
65def invoke_subcommand(args, parser, main_command_usage, subcommands):
66 """
67 Dispatch to subcommand handler borrowed from combo-layer.
68 Should use argparse, but has to work in 2.6.
69 """
70 if not args.command:
71 logger.error("No subcommand specified, exiting")
72 parser.print_help()
73 return 1
74 elif args.command == "help":
75 wic_help(args, main_command_usage, subcommands)
76 elif args.command not in subcommands:
77 logger.error("Unsupported subcommand %s, exiting\n", args.command)
78 parser.print_help()
79 return 1
80 else:
81 subcmd = subcommands.get(args.command, subcommand_error)
82 usage = subcmd[1]
83 subcmd[0](args, usage)
84
85
86##
87# wic help and usage strings
88##
89
90wic_usage = """
91
92 Create a customized OpenEmbedded image
93
94 usage: wic [--version] | [--help] | [COMMAND [ARGS]]
95
96 Current 'wic' commands are:
97 help Show help for command or one of the topics (see below)
98 create Create a new OpenEmbedded image
99 list List available canned images and source plugins
100
101 Help topics:
102 overview wic overview - General overview of wic
103 plugins wic plugins - Overview and API
104 kickstart wic kickstart - wic kickstart reference
105"""
106
107wic_help_usage = """
108
109 usage: wic help <subcommand>
110
111 This command displays detailed help for the specified subcommand.
112"""
113
114wic_create_usage = """
115
116 Create a new OpenEmbedded image
117
118 usage: wic create <wks file or image name> [-o <DIRNAME> | --outdir <DIRNAME>]
119 [-e | --image-name] [-s, --skip-build-check] [-D, --debug]
120 [-r, --rootfs-dir] [-b, --bootimg-dir]
121 [-k, --kernel-dir] [-n, --native-sysroot] [-f, --build-rootfs]
122 [-c, --compress-with] [-m, --bmap]
123
124 This command creates an OpenEmbedded image based on the 'OE kickstart
125 commands' found in the <wks file>.
126
127 The -o option can be used to place the image in a directory with a
128 different name and location.
129
130 See 'wic help create' for more detailed instructions.
131"""
132
133wic_create_help = """
134
135NAME
136 wic create - Create a new OpenEmbedded image
137
138SYNOPSIS
139 wic create <wks file or image name> [-o <DIRNAME> | --outdir <DIRNAME>]
140 [-e | --image-name] [-s, --skip-build-check] [-D, --debug]
141 [-r, --rootfs-dir] [-b, --bootimg-dir]
142 [-k, --kernel-dir] [-n, --native-sysroot] [-f, --build-rootfs]
143 [-c, --compress-with] [-m, --bmap] [--no-fstab-update]
144
145DESCRIPTION
146 This command creates an OpenEmbedded image based on the 'OE
147 kickstart commands' found in the <wks file>.
148
149 In order to do this, wic needs to know the locations of the
150 various build artifacts required to build the image.
151
152 Users can explicitly specify the build artifact locations using
153 the -r, -b, -k, and -n options. See below for details on where
154 the corresponding artifacts are typically found in a normal
155 OpenEmbedded build.
156
157 Alternatively, users can use the -e option to have 'wic' determine
158 those locations for a given image. If the -e option is used, the
159 user needs to have set the appropriate MACHINE variable in
160 local.conf, and have sourced the build environment.
161
162 The -e option is used to specify the name of the image to use the
163 artifacts from e.g. core-image-sato.
164
165 The -r option is used to specify the path to the /rootfs dir to
166 use as the .wks rootfs source.
167
168 The -b option is used to specify the path to the dir containing
169 the boot artifacts (e.g. /EFI or /syslinux dirs) to use as the
170 .wks bootimg source.
171
172 The -k option is used to specify the path to the dir containing
173 the kernel to use in the .wks bootimg.
174
175 The -n option is used to specify the path to the native sysroot
176 containing the tools to use to build the image.
177
178 The -f option is used to build rootfs by running "bitbake <image>"
179
180 The -s option is used to skip the build check. The build check is
181 a simple sanity check used to determine whether the user has
182 sourced the build environment so that the -e option can operate
183 correctly. If the user has specified the build artifact locations
184 explicitly, 'wic' assumes the user knows what he or she is doing
185 and skips the build check.
186
187 The -D option is used to display debug information detailing
188 exactly what happens behind the scenes when a create request is
189 fulfilled (or not, as the case may be). It enumerates and
190 displays the command sequence used, and should be included in any
191 bug report describing unexpected results.
192
193 When 'wic -e' is used, the locations for the build artifacts
194 values are determined by 'wic -e' from the output of the 'bitbake
195 -e' command given an image name e.g. 'core-image-minimal' and a
196 given machine set in local.conf. In that case, the image is
197 created as if the following 'bitbake -e' variables were used:
198
199 -r: IMAGE_ROOTFS
200 -k: STAGING_KERNEL_DIR
201 -n: STAGING_DIR_NATIVE
202 -b: empty (plugin-specific handlers must determine this)
203
204 If 'wic -e' is not used, the user needs to select the appropriate
205 value for -b (as well as -r, -k, and -n).
206
207 The -o option can be used to place the image in a directory with a
208 different name and location.
209
210 The -c option is used to specify compressor utility to compress
211 an image. gzip, bzip2 and xz compressors are supported.
212
213 The -m option is used to produce .bmap file for the image. This file
214 can be used to flash image using bmaptool utility.
215
216 The --no-fstab-update option is used to doesn't change fstab file. When
217 using this option the final fstab file will be same that in rootfs and
218 wic doesn't update file, e.g adding a new mount point. User can control
219 the fstab file content in base-files recipe.
220"""
221
222wic_list_usage = """
223
224 List available OpenEmbedded images and source plugins
225
226 usage: wic list images
227 wic list <image> help
228 wic list source-plugins
229
230 This command enumerates the set of available canned images as well as
231 help for those images. It also can be used to list of available source
232 plugins.
233
234 The first form enumerates all the available 'canned' images.
235
236 The second form lists the detailed help information for a specific
237 'canned' image.
238
239 The third form enumerates all the available --sources (source
240 plugins).
241
242 See 'wic help list' for more details.
243"""
244
245wic_list_help = """
246
247NAME
248 wic list - List available OpenEmbedded images and source plugins
249
250SYNOPSIS
251 wic list images
252 wic list <image> help
253 wic list source-plugins
254
255DESCRIPTION
256 This command enumerates the set of available canned images as well
257 as help for those images. It also can be used to list available
258 source plugins.
259
260 The first form enumerates all the available 'canned' images.
261 These are actually just the set of .wks files that have been moved
262 into the /scripts/lib/wic/canned-wks directory).
263
264 The second form lists the detailed help information for a specific
265 'canned' image.
266
267 The third form enumerates all the available --sources (source
268 plugins). The contents of a given partition are driven by code
269 defined in 'source plugins'. Users specify a specific plugin via
270 the --source parameter of the partition .wks command. Normally
271 this is the 'rootfs' plugin but can be any of the more specialized
272 sources listed by the 'list source-plugins' command. Users can
273 also add their own source plugins - see 'wic help plugins' for
274 details.
275"""
276
277wic_ls_usage = """
278
279 List content of a partitioned image
280
281 usage: wic ls <image>[:<partition>[<path>]] [--native-sysroot <path>]
282
283 This command outputs either list of image partitions or directory contents
284 of vfat and ext* partitions.
285
286 See 'wic help ls' for more detailed instructions.
287
288"""
289
290wic_ls_help = """
291
292NAME
293 wic ls - List contents of partitioned image or partition
294
295SYNOPSIS
296 wic ls <image>
297 wic ls <image>:<vfat or ext* partition>
298 wic ls <image>:<vfat or ext* partition><path>
299 wic ls <image>:<vfat or ext* partition><path> --native-sysroot <path>
300
301DESCRIPTION
302 This command lists either partitions of the image or directory contents
303 of vfat or ext* partitions.
304
305 The first form it lists partitions of the image.
306 For example:
307 $ wic ls tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic
308 Num Start End Size Fstype
309 1 1048576 24438783 23390208 fat16
310 2 25165824 50315263 25149440 ext4
311
312 Second and third form list directory content of the partition:
313 $ wic ls tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1
314 Volume in drive : is boot
315 Volume Serial Number is 2DF2-5F02
316 Directory for ::/
317
318 efi <DIR> 2017-05-11 10:54
319 startup nsh 26 2017-05-11 10:54
320 vmlinuz 6922288 2017-05-11 10:54
321 3 files 6 922 314 bytes
322 15 818 752 bytes free
323
324
325 $ wic ls tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1/EFI/boot/
326 Volume in drive : is boot
327 Volume Serial Number is 2DF2-5F02
328 Directory for ::/EFI/boot
329
330 . <DIR> 2017-05-11 10:54
331 .. <DIR> 2017-05-11 10:54
332 grub cfg 679 2017-05-11 10:54
333 bootx64 efi 571392 2017-05-11 10:54
334 4 files 572 071 bytes
335 15 818 752 bytes free
336
337 The -n option is used to specify the path to the native sysroot
338 containing the tools(parted and mtools) to use.
339
340"""
341
342wic_cp_usage = """
343
344 Copy files and directories to/from the vfat or ext* partition
345
346 usage: wic cp <src> <dest> [--native-sysroot <path>]
347
348 source/destination image in format <image>:<partition>[<path>]
349
350 This command copies files or directories either
351 - from local to vfat or ext* partitions of partitioned image
352 - from vfat or ext* partitions of partitioned image to local
353
354 See 'wic help cp' for more detailed instructions.
355
356"""
357
358wic_cp_help = """
359
360NAME
361 wic cp - copy files and directories to/from the vfat or ext* partitions
362
363SYNOPSIS
364 wic cp <src> <dest>:<partition>
365 wic cp <src>:<partition> <dest>
366 wic cp <src> <dest-image>:<partition><path>
367 wic cp <src> <dest-image>:<partition><path> --native-sysroot <path>
368
369DESCRIPTION
370 This command copies files or directories either
371 - from local to vfat or ext* partitions of partitioned image
372 - from vfat or ext* partitions of partitioned image to local
373
374 The first form of it copies file or directory to the root directory of
375 the partition:
376 $ wic cp test.wks tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1
377 $ wic ls tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1
378 Volume in drive : is boot
379 Volume Serial Number is DB4C-FD4C
380 Directory for ::/
381
382 efi <DIR> 2017-05-24 18:15
383 loader <DIR> 2017-05-24 18:15
384 startup nsh 26 2017-05-24 18:15
385 vmlinuz 6926384 2017-05-24 18:15
386 test wks 628 2017-05-24 21:22
387 5 files 6 927 038 bytes
388 15 677 440 bytes free
389
390 The second form of the command copies file or directory to the specified directory
391 on the partition:
392 $ wic cp test tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1/efi/
393 $ wic ls tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1/efi/
394 Volume in drive : is boot
395 Volume Serial Number is DB4C-FD4C
396 Directory for ::/efi
397
398 . <DIR> 2017-05-24 18:15
399 .. <DIR> 2017-05-24 18:15
400 boot <DIR> 2017-05-24 18:15
401 test <DIR> 2017-05-24 21:27
402 4 files 0 bytes
403 15 675 392 bytes free
404
405 The third form of the command copies file or directory from the specified directory
406 on the partition to local:
407 $ wic cp tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1/vmlinuz test
408
409 The -n option is used to specify the path to the native sysroot
410 containing the tools(parted and mtools) to use.
411"""
412
413wic_rm_usage = """
414
415 Remove files or directories from the vfat or ext* partitions
416
417 usage: wic rm <image>:<partition><path> [--native-sysroot <path>]
418
419 This command removes files or directories from the vfat or ext* partitions of
420 the partitioned image.
421
422 See 'wic help rm' for more detailed instructions.
423
424"""
425
426wic_rm_help = """
427
428NAME
429 wic rm - remove files or directories from the vfat or ext* partitions
430
431SYNOPSIS
432 wic rm <src> <image>:<partition><path>
433 wic rm <src> <image>:<partition><path> --native-sysroot <path>
434 wic rm -r <image>:<partition><path>
435
436DESCRIPTION
437 This command removes files or directories from the vfat or ext* partition of the
438 partitioned image:
439
440 $ wic ls ./tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1
441 Volume in drive : is boot
442 Volume Serial Number is 11D0-DE21
443 Directory for ::/
444
445 libcom32 c32 186500 2017-06-02 15:15
446 libutil c32 24148 2017-06-02 15:15
447 syslinux cfg 209 2017-06-02 15:15
448 vesamenu c32 27104 2017-06-02 15:15
449 vmlinuz 6926384 2017-06-02 15:15
450 5 files 7 164 345 bytes
451 16 582 656 bytes free
452
453 $ wic rm ./tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1/libutil.c32
454
455 $ wic ls ./tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic:1
456 Volume in drive : is boot
457 Volume Serial Number is 11D0-DE21
458 Directory for ::/
459
460 libcom32 c32 186500 2017-06-02 15:15
461 syslinux cfg 209 2017-06-02 15:15
462 vesamenu c32 27104 2017-06-02 15:15
463 vmlinuz 6926384 2017-06-02 15:15
464 4 files 7 140 197 bytes
465 16 607 232 bytes free
466
467 The -n option is used to specify the path to the native sysroot
468 containing the tools(parted and mtools) to use.
469
470 The -r option is used to remove directories and their contents
471 recursively,this only applies to ext* partition.
472"""
473
474wic_write_usage = """
475
476 Write image to a device
477
478 usage: wic write <image> <target device> [--expand [rules]] [--native-sysroot <path>]
479
480 This command writes partitioned image to a target device (USB stick, SD card etc).
481
482 See 'wic help write' for more detailed instructions.
483
484"""
485
486wic_write_help = """
487
488NAME
489 wic write - write an image to a device
490
491SYNOPSIS
492 wic write <image> <target>
493 wic write <image> <target> --expand auto
494 wic write <image> <target> --expand 1:100M,2:300M
495 wic write <image> <target> --native-sysroot <path>
496
497DESCRIPTION
498 This command writes an image to a target device (USB stick, SD card etc)
499
500 $ wic write ./tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic /dev/sdb
501
502 The --expand option is used to resize image partitions.
503 --expand auto expands partitions to occupy all free space available on the target device.
504 It's also possible to specify expansion rules in a format
505 <partition>:<size>[,<partition>:<size>...] for one or more partitions.
506 Specifying size 0 will keep partition unmodified.
507 Note: Resizing boot partition can result in non-bootable image for non-EFI images. It is
508 recommended to use size 0 for boot partition to keep image bootable.
509
510 The --native-sysroot option is used to specify the path to the native sysroot
511 containing the tools(parted, resize2fs) to use.
512"""
513
514wic_plugins_help = """
515
516NAME
517 wic plugins - Overview and API
518
519DESCRIPTION
520 plugins allow wic functionality to be extended and specialized by
521 users. This section documents the plugin interface, which is
522 currently restricted to 'source' plugins.
523
524 'Source' plugins provide a mechanism to customize various aspects
525 of the image generation process in wic, mainly the contents of
526 partitions.
527
528 Source plugins provide a mechanism for mapping values specified in
529 .wks files using the --source keyword to a particular plugin
530 implementation that populates a corresponding partition.
531
532 A source plugin is created as a subclass of SourcePlugin (see
533 scripts/lib/wic/pluginbase.py) and the plugin file containing it
534 is added to scripts/lib/wic/plugins/source/ to make the plugin
535 implementation available to the wic implementation.
536
537 Source plugins can also be implemented and added by external
538 layers - any plugins found in a scripts/lib/wic/plugins/source/
539 or lib/wic/plugins/source/ directory in an external layer will
540 also be made available.
541
542 When the wic implementation needs to invoke a partition-specific
543 implementation, it looks for the plugin that has the same name as
544 the --source param given to that partition. For example, if the
545 partition is set up like this:
546
547 part /boot --source bootimg-pcbios ...
548
549 then the methods defined as class members of the plugin having the
550 matching bootimg-pcbios .name class member would be used.
551
552 To be more concrete, here's the plugin definition that would match
553 a '--source bootimg-pcbios' usage, along with an example method
554 that would be called by the wic implementation when it needed to
555 invoke an implementation-specific partition-preparation function:
556
557 class BootimgPcbiosPlugin(SourcePlugin):
558 name = 'bootimg-pcbios'
559
560 @classmethod
561 def do_prepare_partition(self, part, ...)
562
563 If the subclass itself doesn't implement a function, a 'default'
564 version in a superclass will be located and used, which is why all
565 plugins must be derived from SourcePlugin.
566
567 The SourcePlugin class defines the following methods, which is the
568 current set of methods that can be implemented/overridden by
569 --source plugins. Any methods not implemented by a SourcePlugin
570 subclass inherit the implementations present in the SourcePlugin
571 class (see the SourcePlugin source for details):
572
573 do_prepare_partition()
574 Called to do the actual content population for a
575 partition. In other words, it 'prepares' the final partition
576 image which will be incorporated into the disk image.
577
578 do_post_partition()
579 Called after the partition is created. It is useful to add post
580 operations e.g. signing the partition.
581
582 do_configure_partition()
583 Called before do_prepare_partition(), typically used to
584 create custom configuration files for a partition, for
585 example syslinux or grub config files.
586
587 do_install_disk()
588 Called after all partitions have been prepared and assembled
589 into a disk image. This provides a hook to allow
590 finalization of a disk image, for example to write an MBR to
591 it.
592
593 do_stage_partition()
594 Special content-staging hook called before
595 do_prepare_partition(), normally empty.
596
597 Typically, a partition will just use the passed-in
598 parameters, for example the unmodified value of bootimg_dir.
599 In some cases however, things may need to be more tailored.
600 As an example, certain files may additionally need to be
601 take from bootimg_dir + /boot. This hook allows those files
602 to be staged in a customized fashion. Note that
603 get_bitbake_var() allows you to access non-standard
604 variables that you might want to use for these types of
605 situations.
606
607 This scheme is extensible - adding more hooks is a simple matter
608 of adding more plugin methods to SourcePlugin and derived classes.
609 Please see the implementation for details.
610"""
611
612wic_overview_help = """
613
614NAME
615 wic overview - General overview of wic
616
617DESCRIPTION
618 The 'wic' command generates partitioned images from existing
619 OpenEmbedded build artifacts. Image generation is driven by
620 partitioning commands contained in an 'Openembedded kickstart'
621 (.wks) file (see 'wic help kickstart') specified either directly
622 on the command-line or as one of a selection of canned .wks files
623 (see 'wic list images'). When applied to a given set of build
624 artifacts, the result is an image or set of images that can be
625 directly written onto media and used on a particular system.
626
627 The 'wic' command and the infrastructure it's based on is by
628 definition incomplete - its purpose is to allow the generation of
629 customized images, and as such was designed to be completely
630 extensible via a plugin interface (see 'wic help plugins').
631
632 Background and Motivation
633
634 wic is meant to be a completely independent standalone utility
635 that initially provides easier-to-use and more flexible
636 replacements for a couple bits of existing functionality in
637 oe-core: directdisk.bbclass and mkefidisk.sh. The difference
638 between wic and those examples is that with wic the functionality
639 of those scripts is implemented by a general-purpose partitioning
640 'language' based on Red Hat kickstart syntax).
641
642 The initial motivation and design considerations that lead to the
643 current tool are described exhaustively in Yocto Bug #3847
644 (https://bugzilla.yoctoproject.org/show_bug.cgi?id=3847).
645
646 Implementation and Examples
647
648 wic can be used in two different modes, depending on how much
649 control the user needs in specifying the Openembedded build
650 artifacts that will be used in creating the image: 'raw' and
651 'cooked'.
652
653 If used in 'raw' mode, artifacts are explicitly specified via
654 command-line arguments (see example below).
655
656 The more easily usable 'cooked' mode uses the current MACHINE
657 setting and a specified image name to automatically locate the
658 artifacts used to create the image.
659
660 OE kickstart files (.wks) can of course be specified directly on
661 the command-line, but the user can also choose from a set of
662 'canned' .wks files available via the 'wic list images' command
663 (example below).
664
665 In any case, the prerequisite for generating any image is to have
666 the build artifacts already available. The below examples assume
667 the user has already build a 'core-image-minimal' for a specific
668 machine (future versions won't require this redundant step, but
669 for now that's typically how build artifacts get generated).
670
671 The other prerequisite is to source the build environment:
672
673 $ source oe-init-build-env
674
675 To start out with, we'll generate an image from one of the canned
676 .wks files. The following generates a list of availailable
677 images:
678
679 $ wic list images
680 mkefidisk Create an EFI disk image
681 directdisk Create a 'pcbios' direct disk image
682
683 You can get more information about any of the available images by
684 typing 'wic list xxx help', where 'xxx' is one of the image names:
685
686 $ wic list mkefidisk help
687
688 Creates a partitioned EFI disk image that the user can directly dd
689 to boot media.
690
691 At any time, you can get help on the 'wic' command or any
692 subcommand (currently 'list' and 'create'). For instance, to get
693 the description of 'wic create' command and its parameters:
694
695 $ wic create
696
697 Usage:
698
699 Create a new OpenEmbedded image
700
701 usage: wic create <wks file or image name> [-o <DIRNAME> | ...]
702 [-i <JSON PROPERTY FILE> | --infile <JSON PROPERTY_FILE>]
703 [-e | --image-name] [-s, --skip-build-check] [-D, --debug]
704 [-r, --rootfs-dir] [-b, --bootimg-dir] [-k, --kernel-dir]
705 [-n, --native-sysroot] [-f, --build-rootfs]
706
707 This command creates an OpenEmbedded image based on the 'OE
708 kickstart commands' found in the <wks file>.
709
710 The -o option can be used to place the image in a directory
711 with a different name and location.
712
713 See 'wic help create' for more detailed instructions.
714 ...
715
716 As mentioned in the command, you can get even more detailed
717 information by adding 'help' to the above:
718
719 $ wic help create
720
721 So, the easiest way to create an image is to use the -e option
722 with a canned .wks file. To use the -e option, you need to
723 specify the image used to generate the artifacts and you actually
724 need to have the MACHINE used to build them specified in your
725 local.conf (these requirements aren't necessary if you aren't
726 using the -e options.) Below, we generate a directdisk image,
727 pointing the process at the core-image-minimal artifacts for the
728 current MACHINE:
729
730 $ wic create directdisk -e core-image-minimal
731
732 Checking basic build environment...
733 Done.
734
735 Creating image(s)...
736
737 Info: The new image(s) can be found here:
738 /var/tmp/wic/build/directdisk-201309252350-sda.direct
739
740 The following build artifacts were used to create the image(s):
741
742 ROOTFS_DIR: ...
743 BOOTIMG_DIR: ...
744 KERNEL_DIR: ...
745 NATIVE_SYSROOT: ...
746
747 The image(s) were created using OE kickstart file:
748 .../scripts/lib/wic/canned-wks/directdisk.wks
749
750 The output shows the name and location of the image created, and
751 so that you know exactly what was used to generate the image, each
752 of the artifacts and the kickstart file used.
753
754 Similarly, you can create a 'mkefidisk' image in the same way
755 (notice that this example uses a different machine - because it's
756 using the -e option, you need to change the MACHINE in your
757 local.conf):
758
759 $ wic create mkefidisk -e core-image-minimal
760 Checking basic build environment...
761 Done.
762
763 Creating image(s)...
764
765 Info: The new image(s) can be found here:
766 /var/tmp/wic/build/mkefidisk-201309260027-sda.direct
767
768 ...
769
770 Here's an example that doesn't take the easy way out and manually
771 specifies each build artifact, along with a non-canned .wks file,
772 and also uses the -o option to have wic create the output
773 somewhere other than the default /var/tmp/wic:
774
775 $ wic create ./test.wks -o ./out --rootfs-dir
776 tmp/work/qemux86_64-poky-linux/core-image-minimal/1.0-r0/rootfs
777 --bootimg-dir tmp/sysroots/qemux86-64/usr/share
778 --kernel-dir tmp/deploy/images/qemux86-64
779 --native-sysroot tmp/sysroots/x86_64-linux
780
781 Creating image(s)...
782
783 Info: The new image(s) can be found here:
784 out/build/test-201507211313-sda.direct
785
786 The following build artifacts were used to create the image(s):
787 ROOTFS_DIR: tmp/work/qemux86_64-poky-linux/core-image-minimal/1.0-r0/rootfs
788 BOOTIMG_DIR: tmp/sysroots/qemux86-64/usr/share
789 KERNEL_DIR: tmp/deploy/images/qemux86-64
790 NATIVE_SYSROOT: tmp/sysroots/x86_64-linux
791
792 The image(s) were created using OE kickstart file:
793 ./test.wks
794
795 Here is a content of test.wks:
796
797 part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
798 part / --source rootfs --ondisk sda --fstype=ext3 --label platform --align 1024
799
800 bootloader --timeout=0 --append="rootwait rootfstype=ext3 video=vesafb vga=0x318 console=tty0"
801
802
803 Finally, here's an example of the actual partition language
804 commands used to generate the mkefidisk image i.e. these are the
805 contents of the mkefidisk.wks OE kickstart file:
806
807 # short-description: Create an EFI disk image
808 # long-description: Creates a partitioned EFI disk image that the user
809 # can directly dd to boot media.
810
811 part /boot --source bootimg-efi --ondisk sda --fstype=efi --active
812
813 part / --source rootfs --ondisk sda --fstype=ext3 --label platform
814
815 part swap --ondisk sda --size 44 --label swap1 --fstype=swap
816
817 bootloader --timeout=10 --append="rootwait console=ttyPCH0,115200"
818
819 You can get a complete listing and description of all the
820 kickstart commands available for use in .wks files from 'wic help
821 kickstart'.
822"""
823
824wic_kickstart_help = """
825
826NAME
827 wic kickstart - wic kickstart reference
828
829DESCRIPTION
830 This section provides the definitive reference to the wic
831 kickstart language. It also provides documentation on the list of
832 --source plugins available for use from the 'part' command (see
833 the 'Platform-specific Plugins' section below).
834
835 The current wic implementation supports only the basic kickstart
836 partitioning commands: partition (or part for short) and
837 bootloader.
838
839 The following is a listing of the commands, their syntax, and
840 meanings. The commands are based on the Fedora kickstart
841 documentation but with modifications to reflect wic capabilities.
842
843 https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#part-or-partition
844 https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#bootloader
845
846 Commands
847
848 * 'part' or 'partition'
849
850 This command creates a partition on the system and uses the
851 following syntax:
852
853 part [<mountpoint>]
854
855 The <mountpoint> is where the partition will be mounted and
856 must take of one of the following forms:
857
858 /<path>: For example: /, /usr, or /home
859
860 swap: The partition will be used as swap space.
861
862 If a <mountpoint> is not specified the partition will be created
863 but will not be mounted.
864
865 Partitions with a <mountpoint> specified will be automatically mounted.
866 This is achieved by wic adding entries to the fstab during image
867 generation. In order for a valid fstab to be generated one of the
868 --ondrive, --ondisk, --use-uuid or --use-label partition options must
869 be used for each partition that specifies a mountpoint. Note that with
870 --use-{uuid,label} and non-root <mountpoint>, including swap, the mount
871 program must understand the PARTUUID or LABEL syntax. This currently
872 excludes the busybox versions of these applications.
873
874
875 The following are supported 'part' options:
876
877 --size: The minimum partition size. Specify an integer value
878 such as 500. Multipliers k, M ang G can be used. If
879 not specified, the size is in MB.
880 You do not need this option if you use --source.
881
882 --fixed-size: Exact partition size. Value format is the same
883 as for --size option. This option cannot be
884 specified along with --size. If partition data
885 is larger than --fixed-size and error will be
886 raised when assembling disk image.
887
888 --source: This option is a wic-specific option that names the
889 source of the data that will populate the
890 partition. The most common value for this option
891 is 'rootfs', but can be any value which maps to a
892 valid 'source plugin' (see 'wic help plugins').
893
894 If '--source rootfs' is used, it tells the wic
895 command to create a partition as large as needed
896 and to fill it with the contents of the root
897 filesystem pointed to by the '-r' wic command-line
898 option (or the equivalent rootfs derived from the
899 '-e' command-line option). The filesystem type
900 that will be used to create the partition is driven
901 by the value of the --fstype option specified for
902 the partition (see --fstype below).
903
904 If --source <plugin-name>' is used, it tells the
905 wic command to create a partition as large as
906 needed and to fill with the contents of the
907 partition that will be generated by the specified
908 plugin name using the data pointed to by the '-r'
909 wic command-line option (or the equivalent rootfs
910 derived from the '-e' command-line option).
911 Exactly what those contents and filesystem type end
912 up being are dependent on the given plugin
913 implementation.
914
915 If --source option is not used, the wic command
916 will create empty partition. --size parameter has
917 to be used to specify size of empty partition.
918
919 --ondisk or --ondrive: Forces the partition to be created on
920 a particular disk.
921
922 --fstype: Sets the file system type for the partition. These
923 apply to partitions created using '--source rootfs' (see
924 --source above). Valid values are:
925
926 vfat
927 msdos
928 ext2
929 ext3
930 ext4
931 btrfs
932 squashfs
933 erofs
934 swap
935
936 --fsoptions: Specifies a free-form string of options to be
937 used when mounting the filesystem. This string
938 will be copied into the /etc/fstab file of the
939 installed system and should be enclosed in
940 quotes. If not specified, the default string is
941 "defaults".
942
943 --fspassno: Specifies the order in which filesystem checks are done
944 at boot time by fsck. See fs_passno parameter of
945 fstab(5). This parameter will be copied into the
946 /etc/fstab file of the installed system. If not
947 specified the default value of "0" will be used.
948
949 --label label: Specifies the label to give to the filesystem
950 to be made on the partition. If the given
951 label is already in use by another filesystem,
952 a new label is created for the partition.
953
954 --use-label: This option is specific to wic. It makes wic to use the
955 label in /etc/fstab to specify a partition. If the
956 --use-label and --use-uuid are used at the same time,
957 we prefer the uuid because it is less likely to cause
958 name confliction. We don't support using this parameter
959 on the root partition since it requires an initramfs to
960 parse this value and we do not currently support that.
961
962 --active: Marks the partition as active.
963
964 --align (in KBytes): This option is specific to wic and says
965 to start a partition on an x KBytes
966 boundary.
967
968 --no-table: This option is specific to wic. Space will be
969 reserved for the partition and it will be
970 populated but it will not be added to the
971 partition table. It may be useful for
972 bootloaders.
973
974 --exclude-path: This option is specific to wic. It excludes the given
975 relative path from the resulting image. If the path
976 ends with a slash, only the content of the directory
977 is omitted, not the directory itself. This option only
978 has an effect with the rootfs source plugin.
979
980 --include-path: This option is specific to wic. It adds the contents
981 of the given path or a rootfs to the resulting image.
982 The option contains two fields, the origin and the
983 destination. When the origin is a rootfs, it follows
984 the same logic as the rootfs-dir argument and the
985 permissions and owners are kept. When the origin is a
986 path, it is relative to the directory in which wic is
987 running not the rootfs itself so use of an absolute
988 path is recommended, and the owner and group is set to
989 root:root. If no destination is given it is
990 automatically set to the root of the rootfs. This
991 option only has an effect with the rootfs source
992 plugin.
993
994 --change-directory: This option is specific to wic. It changes to the
995 given directory before copying the files. This
996 option is useful when we want to split a rootfs in
997 multiple partitions and we want to keep the right
998 permissions and usernames in all the partitions.
999
1000 --no-fstab-update: This option is specific to wic. It does not update the
1001 '/etc/fstab' stock file for the given partition.
1002
1003 --extra-space: This option is specific to wic. It adds extra
1004 space after the space filled by the content
1005 of the partition. The final size can go
1006 beyond the size specified by --size.
1007 By default, 10MB. This option cannot be used
1008 with --fixed-size option.
1009
1010 --overhead-factor: This option is specific to wic. The
1011 size of the partition is multiplied by
1012 this factor. It has to be greater than or
1013 equal to 1. The default value is 1.3.
1014 This option cannot be used with --fixed-size
1015 option.
1016
1017 --part-name: This option is specific to wic. It specifies name for GPT partitions.
1018
1019 --part-type: This option is specific to wic. It specifies partition
1020 type GUID for GPT partitions.
1021 List of partition type GUIDS can be found here:
1022 http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
1023
1024 --use-uuid: This option is specific to wic. It makes wic to generate
1025 random globally unique identifier (GUID) for the partition
1026 and use it in bootloader configuration to specify root partition.
1027
1028 --uuid: This option is specific to wic. It specifies partition UUID.
1029 It's useful if preconfigured partition UUID is added to kernel command line
1030 in bootloader configuration before running wic. In this case .wks file can
1031 be generated or modified to set preconfigured parition UUID using this option.
1032
1033 --fsuuid: This option is specific to wic. It specifies filesystem UUID.
1034 It's useful if preconfigured filesystem UUID is added to kernel command line
1035 in bootloader configuration before running wic. In this case .wks file can
1036 be generated or modified to set preconfigured filesystem UUID using this option.
1037
1038 --system-id: This option is specific to wic. It specifies partition system id. It's useful
1039 for the harware that requires non-default partition system ids. The parameter
1040 in one byte long hex number either with 0x prefix or without it.
1041
1042 --mkfs-extraopts: This option specifies extra options to pass to mkfs utility.
1043 NOTE, that wic uses default options for some filesystems, for example
1044 '-S 512' for mkfs.fat or '-F -i 8192' for mkfs.ext. Those options will
1045 not take effect when --mkfs-extraopts is used. This should be taken into
1046 account when using --mkfs-extraopts.
1047
1048 * bootloader
1049
1050 This command allows the user to specify various bootloader
1051 options. The following are supported 'bootloader' options:
1052
1053 --timeout: Specifies the number of seconds before the
1054 bootloader times out and boots the default option.
1055
1056 --append: Specifies kernel parameters. These will be added to
1057 bootloader command-line - for example, the syslinux
1058 APPEND or grub kernel command line.
1059
1060 --configfile: Specifies a user defined configuration file for
1061 the bootloader. This file must be located in the
1062 canned-wks folder or could be the full path to the
1063 file. Using this option will override any other
1064 bootloader option.
1065
1066 Note that bootloader functionality and boot partitions are
1067 implemented by the various --source plugins that implement
1068 bootloader functionality; the bootloader command essentially
1069 provides a means of modifying bootloader configuration.
1070
1071 * include
1072
1073 This command allows the user to include the content of .wks file
1074 into original .wks file.
1075
1076 Command uses the following syntax:
1077
1078 include <file>
1079
1080 The <file> is either path to the file or its name. If name is
1081 specified wic will try to find file in the directories with canned
1082 .wks files.
1083
1084"""
1085
1086wic_help_help = """
1087NAME
1088 wic help - display a help topic
1089
1090DESCRIPTION
1091 Specify a help topic to display it. Topics are shown above.
1092"""
1093
1094
1095wic_help = """
1096Creates a customized OpenEmbedded image.
1097
1098Usage: wic [--version]
1099 wic help [COMMAND or TOPIC]
1100 wic COMMAND [ARGS]
1101
1102 usage 1: Returns the current version of Wic
1103 usage 2: Returns detailed help for a COMMAND or TOPIC
1104 usage 3: Executes COMMAND
1105
1106
1107COMMAND:
1108
1109 list - List available canned images and source plugins
1110 ls - List contents of partitioned image or partition
1111 rm - Remove files or directories from the vfat or ext* partitions
1112 help - Show help for a wic COMMAND or TOPIC
1113 write - Write an image to a device
1114 cp - Copy files and directories to the vfat or ext* partitions
1115 create - Create a new OpenEmbedded image
1116
1117
1118TOPIC:
1119 overview - Presents an overall overview of Wic
1120 plugins - Presents an overview and API for Wic plugins
1121 kickstart - Presents a Wic kickstart file reference
1122
1123
1124Examples:
1125
1126 $ wic --version
1127
1128 Returns the current version of Wic
1129
1130
1131 $ wic help cp
1132
1133 Returns the SYNOPSIS and DESCRIPTION for the Wic "cp" command.
1134
1135
1136 $ wic list images
1137
1138 Returns the list of canned images (i.e. *.wks files located in
1139 the /scripts/lib/wic/canned-wks directory.
1140
1141
1142 $ wic create mkefidisk -e core-image-minimal
1143
1144 Creates an EFI disk image from artifacts used in a previous
1145 core-image-minimal build in standard BitBake locations
1146 (e.g. Cooked Mode).
1147
1148"""
diff --git a/scripts/lib/wic/ksparser.py b/scripts/lib/wic/ksparser.py
deleted file mode 100644
index 7ef3dc83dd..0000000000
--- a/scripts/lib/wic/ksparser.py
+++ /dev/null
@@ -1,298 +0,0 @@
1#!/usr/bin/env python3
2#
3# Copyright (c) 2016 Intel, Inc.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7# DESCRIPTION
8# This module provides parser for kickstart format
9#
10# AUTHORS
11# Ed Bartosh <ed.bartosh> (at] linux.intel.com>
12
13"""Kickstart parser module."""
14
15import os
16import shlex
17import logging
18import re
19
20from argparse import ArgumentParser, ArgumentError, ArgumentTypeError
21
22from wic.engine import find_canned
23from wic.partition import Partition
24from wic.misc import get_bitbake_var
25
26logger = logging.getLogger('wic')
27
28__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t :]+}")
29
30def expand_line(line):
31 while True:
32 m = __expand_var_regexp__.search(line)
33 if not m:
34 return line
35 key = m.group()[2:-1]
36 val = get_bitbake_var(key)
37 if val is None:
38 logger.warning("cannot expand variable %s" % key)
39 return line
40 line = line[:m.start()] + val + line[m.end():]
41
42class KickStartError(Exception):
43 """Custom exception."""
44 pass
45
46class KickStartParser(ArgumentParser):
47 """
48 This class overwrites error method to throw exception
49 instead of producing usage message(default argparse behavior).
50 """
51 def error(self, message):
52 raise ArgumentError(None, message)
53
54def sizetype(default, size_in_bytes=False):
55 def f(arg):
56 """
57 Custom type for ArgumentParser
58 Converts size string in <num>[S|s|K|k|M|G] format into the integer value
59 """
60 try:
61 suffix = default
62 size = int(arg)
63 except ValueError:
64 try:
65 suffix = arg[-1:]
66 size = int(arg[:-1])
67 except ValueError:
68 raise ArgumentTypeError("Invalid size: %r" % arg)
69
70
71 if size_in_bytes:
72 if suffix == 's' or suffix == 'S':
73 return size * 512
74 mult = 1024
75 else:
76 mult = 1
77
78 if suffix == "k" or suffix == "K":
79 return size * mult
80 if suffix == "M":
81 return size * mult * 1024
82 if suffix == "G":
83 return size * mult * 1024 * 1024
84
85 raise ArgumentTypeError("Invalid size: %r" % arg)
86 return f
87
88def overheadtype(arg):
89 """
90 Custom type for ArgumentParser
91 Converts overhead string to float and checks if it's bigger than 1.0
92 """
93 try:
94 result = float(arg)
95 except ValueError:
96 raise ArgumentTypeError("Invalid value: %r" % arg)
97
98 if result < 1.0:
99 raise ArgumentTypeError("Overhead factor should be > 1.0" % arg)
100
101 return result
102
103def cannedpathtype(arg):
104 """
105 Custom type for ArgumentParser
106 Tries to find file in the list of canned wks paths
107 """
108 scripts_path = os.path.abspath(os.path.dirname(__file__) + '../../..')
109 result = find_canned(scripts_path, arg)
110 if not result:
111 raise ArgumentTypeError("file not found: %s" % arg)
112 return result
113
114def systemidtype(arg):
115 """
116 Custom type for ArgumentParser
117 Checks if the argument sutisfies system id requirements,
118 i.e. if it's one byte long integer > 0
119 """
120 error = "Invalid system type: %s. must be hex "\
121 "between 0x1 and 0xFF" % arg
122 try:
123 result = int(arg, 16)
124 except ValueError:
125 raise ArgumentTypeError(error)
126
127 if result <= 0 or result > 0xff:
128 raise ArgumentTypeError(error)
129
130 return arg
131
132class KickStart():
133 """Kickstart parser implementation."""
134
135 DEFAULT_EXTRA_SPACE = 10*1024
136 DEFAULT_OVERHEAD_FACTOR = 1.3
137
138 def __init__(self, confpath):
139
140 self.partitions = []
141 self.bootloader = None
142 self.lineno = 0
143 self.partnum = 0
144
145 parser = KickStartParser()
146 subparsers = parser.add_subparsers()
147
148 part = subparsers.add_parser('part')
149 part.add_argument('mountpoint', nargs='?')
150 part.add_argument('--active', action='store_true')
151 part.add_argument('--align', type=int)
152 part.add_argument('--offset', type=sizetype("K", True))
153 part.add_argument('--exclude-path', nargs='+')
154 part.add_argument('--include-path', nargs='+', action='append')
155 part.add_argument('--change-directory')
156 part.add_argument("--extra-space", type=sizetype("M"))
157 part.add_argument('--fsoptions', dest='fsopts')
158 part.add_argument('--fspassno', dest='fspassno')
159 part.add_argument('--fstype', default='vfat',
160 choices=('ext2', 'ext3', 'ext4', 'btrfs',
161 'squashfs', 'vfat', 'msdos', 'erofs',
162 'swap', 'none'))
163 part.add_argument('--mkfs-extraopts', default='')
164 part.add_argument('--label')
165 part.add_argument('--use-label', action='store_true')
166 part.add_argument('--no-table', action='store_true')
167 part.add_argument('--ondisk', '--ondrive', dest='disk', default='sda')
168 part.add_argument("--overhead-factor", type=overheadtype)
169 part.add_argument('--part-name')
170 part.add_argument('--part-type')
171 part.add_argument('--rootfs-dir')
172 part.add_argument('--type', default='primary',
173 choices = ('primary', 'logical'))
174 part.add_argument('--hidden', action='store_true')
175
176 # --size and --fixed-size cannot be specified together; options
177 # ----extra-space and --overhead-factor should also raise a parser
178 # --error, but since nesting mutually exclusive groups does not work,
179 # ----extra-space/--overhead-factor are handled later
180 sizeexcl = part.add_mutually_exclusive_group()
181 sizeexcl.add_argument('--size', type=sizetype("M"), default=0)
182 sizeexcl.add_argument('--fixed-size', type=sizetype("M"), default=0)
183
184 part.add_argument('--source')
185 part.add_argument('--sourceparams')
186 part.add_argument('--system-id', type=systemidtype)
187 part.add_argument('--use-uuid', action='store_true')
188 part.add_argument('--uuid')
189 part.add_argument('--fsuuid')
190 part.add_argument('--no-fstab-update', action='store_true')
191 part.add_argument('--mbr', action='store_true')
192
193 bootloader = subparsers.add_parser('bootloader')
194 bootloader.add_argument('--append')
195 bootloader.add_argument('--configfile')
196 bootloader.add_argument('--ptable', choices=('msdos', 'gpt', 'gpt-hybrid'),
197 default='msdos')
198 bootloader.add_argument('--timeout', type=int)
199 bootloader.add_argument('--source')
200
201 include = subparsers.add_parser('include')
202 include.add_argument('path', type=cannedpathtype)
203
204 self._parse(parser, confpath)
205 if not self.bootloader:
206 logger.warning('bootloader config not specified, using defaults\n')
207 self.bootloader = bootloader.parse_args([])
208
209 def _parse(self, parser, confpath):
210 """
211 Parse file in .wks format using provided parser.
212 """
213 with open(confpath) as conf:
214 lineno = 0
215 for line in conf:
216 line = line.strip()
217 lineno += 1
218 if line and line[0] != '#':
219 line = expand_line(line)
220 try:
221 line_args = shlex.split(line)
222 parsed = parser.parse_args(line_args)
223 except ArgumentError as err:
224 raise KickStartError('%s:%d: %s' % \
225 (confpath, lineno, err))
226 if line.startswith('part'):
227 # SquashFS does not support filesystem UUID
228 if parsed.fstype == 'squashfs':
229 if parsed.fsuuid:
230 err = "%s:%d: SquashFS does not support UUID" \
231 % (confpath, lineno)
232 raise KickStartError(err)
233 if parsed.label:
234 err = "%s:%d: SquashFS does not support LABEL" \
235 % (confpath, lineno)
236 raise KickStartError(err)
237 # erofs does not support filesystem labels
238 if parsed.fstype == 'erofs' and parsed.label:
239 err = "%s:%d: erofs does not support LABEL" % (confpath, lineno)
240 raise KickStartError(err)
241 if parsed.fstype == 'msdos' or parsed.fstype == 'vfat':
242 if parsed.fsuuid:
243 if parsed.fsuuid.upper().startswith('0X'):
244 if len(parsed.fsuuid) > 10:
245 err = "%s:%d: fsuuid %s given in wks kickstart file " \
246 "exceeds the length limit for %s filesystem. " \
247 "It should be in the form of a 32 bit hexadecimal" \
248 "number (for example, 0xABCD1234)." \
249 % (confpath, lineno, parsed.fsuuid, parsed.fstype)
250 raise KickStartError(err)
251 elif len(parsed.fsuuid) > 8:
252 err = "%s:%d: fsuuid %s given in wks kickstart file " \
253 "exceeds the length limit for %s filesystem. " \
254 "It should be in the form of a 32 bit hexadecimal" \
255 "number (for example, 0xABCD1234)." \
256 % (confpath, lineno, parsed.fsuuid, parsed.fstype)
257 raise KickStartError(err)
258 if parsed.use_label and not parsed.label:
259 err = "%s:%d: Must set the label with --label" \
260 % (confpath, lineno)
261 raise KickStartError(err)
262 # using ArgumentParser one cannot easily tell if option
263 # was passed as argument, if said option has a default
264 # value; --overhead-factor/--extra-space cannot be used
265 # with --fixed-size, so at least detect when these were
266 # passed with non-0 values ...
267 if parsed.fixed_size:
268 if parsed.overhead_factor or parsed.extra_space:
269 err = "%s:%d: arguments --overhead-factor and --extra-space not "\
270 "allowed with argument --fixed-size" \
271 % (confpath, lineno)
272 raise KickStartError(err)
273 else:
274 # ... and provide defaults if not using
275 # --fixed-size iff given option was not used
276 # (again, one cannot tell if option was passed but
277 # with value equal to 0)
278 if '--overhead-factor' not in line_args:
279 parsed.overhead_factor = self.DEFAULT_OVERHEAD_FACTOR
280 if '--extra-space' not in line_args:
281 parsed.extra_space = self.DEFAULT_EXTRA_SPACE
282
283 self.partnum += 1
284 self.partitions.append(Partition(parsed, self.partnum))
285 elif line.startswith('include'):
286 self._parse(parser, parsed.path)
287 elif line.startswith('bootloader'):
288 if not self.bootloader:
289 self.bootloader = parsed
290 # Concatenate the strings set in APPEND
291 append_var = get_bitbake_var("APPEND")
292 if append_var:
293 self.bootloader.append = ' '.join(filter(None, \
294 (self.bootloader.append, append_var)))
295 else:
296 err = "%s:%d: more than one bootloader specified" \
297 % (confpath, lineno)
298 raise KickStartError(err)
diff --git a/scripts/lib/wic/misc.py b/scripts/lib/wic/misc.py
deleted file mode 100644
index 1a7c140fa6..0000000000
--- a/scripts/lib/wic/misc.py
+++ /dev/null
@@ -1,266 +0,0 @@
1#
2# Copyright (c) 2013, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This module provides a place to collect various wic-related utils
8# for the OpenEmbedded Image Tools.
9#
10# AUTHORS
11# Tom Zanussi <tom.zanussi (at] linux.intel.com>
12#
13"""Miscellaneous functions."""
14
15import logging
16import os
17import re
18import subprocess
19import shutil
20
21from collections import defaultdict
22
23from wic import WicError
24
25logger = logging.getLogger('wic')
26
27# executable -> recipe pairs for exec_native_cmd
28NATIVE_RECIPES = {"bmaptool": "bmaptool",
29 "dumpe2fs": "e2fsprogs",
30 "grub-mkimage": "grub-efi",
31 "isohybrid": "syslinux",
32 "mcopy": "mtools",
33 "mdel" : "mtools",
34 "mdeltree" : "mtools",
35 "mdir" : "mtools",
36 "mkdosfs": "dosfstools",
37 "mkisofs": "cdrtools",
38 "mkfs.btrfs": "btrfs-tools",
39 "mkfs.erofs": "erofs-utils",
40 "mkfs.ext2": "e2fsprogs",
41 "mkfs.ext3": "e2fsprogs",
42 "mkfs.ext4": "e2fsprogs",
43 "mkfs.vfat": "dosfstools",
44 "mksquashfs": "squashfs-tools",
45 "mkswap": "util-linux",
46 "mmd": "mtools",
47 "parted": "parted",
48 "sfdisk": "util-linux",
49 "sgdisk": "gptfdisk",
50 "syslinux": "syslinux",
51 "tar": "tar"
52 }
53
54def runtool(cmdln_or_args):
55 """ wrapper for most of the subprocess calls
56 input:
57 cmdln_or_args: can be both args and cmdln str (shell=True)
58 return:
59 rc, output
60 """
61 if isinstance(cmdln_or_args, list):
62 cmd = cmdln_or_args[0]
63 shell = False
64 else:
65 import shlex
66 cmd = shlex.split(cmdln_or_args)[0]
67 shell = True
68
69 sout = subprocess.PIPE
70 serr = subprocess.STDOUT
71
72 try:
73 process = subprocess.Popen(cmdln_or_args, stdout=sout,
74 stderr=serr, shell=shell)
75 sout, serr = process.communicate()
76 # combine stdout and stderr, filter None out and decode
77 out = ''.join([out.decode('utf-8') for out in [sout, serr] if out])
78 except OSError as err:
79 if err.errno == 2:
80 # [Errno 2] No such file or directory
81 raise WicError('Cannot run command: %s, lost dependency?' % cmd)
82 else:
83 raise # relay
84
85 return process.returncode, out
86
87def _exec_cmd(cmd_and_args, as_shell=False):
88 """
89 Execute command, catching stderr, stdout
90
91 Need to execute as_shell if the command uses wildcards
92 """
93 logger.debug("_exec_cmd: %s", cmd_and_args)
94 args = cmd_and_args.split()
95 logger.debug(args)
96
97 if as_shell:
98 ret, out = runtool(cmd_and_args)
99 else:
100 ret, out = runtool(args)
101 out = out.strip()
102 if ret != 0:
103 raise WicError("_exec_cmd: %s returned '%s' instead of 0\noutput: %s" % \
104 (cmd_and_args, ret, out))
105
106 logger.debug("_exec_cmd: output for %s (rc = %d): %s",
107 cmd_and_args, ret, out)
108
109 return ret, out
110
111
112def exec_cmd(cmd_and_args, as_shell=False):
113 """
114 Execute command, return output
115 """
116 return _exec_cmd(cmd_and_args, as_shell)[1]
117
118def find_executable(cmd, paths):
119 recipe = cmd
120 if recipe in NATIVE_RECIPES:
121 recipe = NATIVE_RECIPES[recipe]
122 provided = get_bitbake_var("ASSUME_PROVIDED")
123 if provided and "%s-native" % recipe in provided:
124 return True
125
126 return shutil.which(cmd, path=paths)
127
128def exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""):
129 """
130 Execute native command, catching stderr, stdout
131
132 Need to execute as_shell if the command uses wildcards
133
134 Always need to execute native commands as_shell
135 """
136 # The reason -1 is used is because there may be "export" commands.
137 args = cmd_and_args.split(';')[-1].split()
138 logger.debug(args)
139
140 if pseudo:
141 cmd_and_args = pseudo + cmd_and_args
142
143 hosttools_dir = get_bitbake_var("HOSTTOOLS_DIR")
144 target_sys = get_bitbake_var("TARGET_SYS")
145
146 native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/usr/bin/%s:%s/bin:%s" % \
147 (native_sysroot, native_sysroot,
148 native_sysroot, native_sysroot, target_sys,
149 native_sysroot, hosttools_dir)
150
151 native_cmd_and_args = "export PATH=%s:$PATH;%s" % \
152 (native_paths, cmd_and_args)
153 logger.debug("exec_native_cmd: %s", native_cmd_and_args)
154
155 # If the command isn't in the native sysroot say we failed.
156 if find_executable(args[0], native_paths):
157 ret, out = _exec_cmd(native_cmd_and_args, True)
158 else:
159 ret = 127
160 out = "can't find native executable %s in %s" % (args[0], native_paths)
161
162 prog = args[0]
163 # shell command-not-found
164 if ret == 127 \
165 or (pseudo and ret == 1 and out == "Can't find '%s' in $PATH." % prog):
166 msg = "A native program %s required to build the image "\
167 "was not found (see details above).\n\n" % prog
168 recipe = NATIVE_RECIPES.get(prog)
169 if recipe:
170 msg += "Please make sure wic-tools have %s-native in its DEPENDS, "\
171 "build it with 'bitbake wic-tools' and try again.\n" % recipe
172 else:
173 msg += "Wic failed to find a recipe to build native %s. Please "\
174 "file a bug against wic.\n" % prog
175 raise WicError(msg)
176
177 return ret, out
178
179BOOTDD_EXTRA_SPACE = 16384
180
181class BitbakeVars(defaultdict):
182 """
183 Container for Bitbake variables.
184 """
185 def __init__(self):
186 defaultdict.__init__(self, dict)
187
188 # default_image and vars_dir attributes should be set from outside
189 self.default_image = None
190 self.vars_dir = None
191
192 def _parse_line(self, line, image, matcher=re.compile(r"^([a-zA-Z0-9\-_+./~]+)=(.*)")):
193 """
194 Parse one line from bitbake -e output or from .env file.
195 Put result key-value pair into the storage.
196 """
197 if "=" not in line:
198 return
199 match = matcher.match(line)
200 if not match:
201 return
202 key, val = match.groups()
203 self[image][key] = val.strip('"')
204
205 def get_var(self, var, image=None, cache=True):
206 """
207 Get bitbake variable from 'bitbake -e' output or from .env file.
208 This is a lazy method, i.e. it runs bitbake or parses file only when
209 only when variable is requested. It also caches results.
210 """
211 if not image:
212 image = self.default_image
213
214 if image not in self:
215 if image and self.vars_dir:
216 fname = os.path.join(self.vars_dir, image + '.env')
217 if os.path.isfile(fname):
218 # parse .env file
219 with open(fname) as varsfile:
220 for line in varsfile:
221 self._parse_line(line, image)
222 else:
223 print("Couldn't get bitbake variable from %s." % fname)
224 print("File %s doesn't exist." % fname)
225 return
226 else:
227 # Get bitbake -e output
228 cmd = "bitbake -e"
229 if image:
230 cmd += " %s" % image
231
232 log_level = logger.getEffectiveLevel()
233 logger.setLevel(logging.INFO)
234 ret, lines = _exec_cmd(cmd)
235 logger.setLevel(log_level)
236
237 if ret:
238 logger.error("Couldn't get '%s' output.", cmd)
239 logger.error("Bitbake failed with error:\n%s\n", lines)
240 return
241
242 # Parse bitbake -e output
243 for line in lines.split('\n'):
244 self._parse_line(line, image)
245
246 # Make first image a default set of variables
247 if cache:
248 images = [key for key in self if key]
249 if len(images) == 1:
250 self[None] = self[image]
251
252 result = self[image].get(var)
253 if not cache:
254 self.pop(image, None)
255
256 return result
257
258# Create BB_VARS singleton
259BB_VARS = BitbakeVars()
260
261def get_bitbake_var(var, image=None, cache=True):
262 """
263 Provide old get_bitbake_var API by wrapping
264 get_var method of BB_VARS singleton.
265 """
266 return BB_VARS.get_var(var, image, cache)
diff --git a/scripts/lib/wic/partition.py b/scripts/lib/wic/partition.py
deleted file mode 100644
index 795707ec5d..0000000000
--- a/scripts/lib/wic/partition.py
+++ /dev/null
@@ -1,542 +0,0 @@
1#
2# Copyright (c) 2013-2016 Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This module provides the OpenEmbedded partition object definitions.
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11# Ed Bartosh <ed.bartosh> (at] linux.intel.com>
12
13import logging
14import os
15import uuid
16
17from wic import WicError
18from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
19from wic.pluginbase import PluginMgr
20
21logger = logging.getLogger('wic')
22
23class Partition():
24
25 def __init__(self, args, lineno):
26 self.args = args
27 self.active = args.active
28 self.align = args.align
29 self.disk = args.disk
30 self.device = None
31 self.extra_space = args.extra_space
32 self.exclude_path = args.exclude_path
33 self.include_path = args.include_path
34 self.change_directory = args.change_directory
35 self.fsopts = args.fsopts
36 self.fspassno = args.fspassno
37 self.fstype = args.fstype
38 self.label = args.label
39 self.use_label = args.use_label
40 self.mkfs_extraopts = args.mkfs_extraopts
41 self.mountpoint = args.mountpoint
42 self.no_table = args.no_table
43 self.num = None
44 self.offset = args.offset
45 self.overhead_factor = args.overhead_factor
46 self.part_name = args.part_name
47 self.part_type = args.part_type
48 self.rootfs_dir = args.rootfs_dir
49 self.size = args.size
50 self.fixed_size = args.fixed_size
51 self.source = args.source
52 self.sourceparams = args.sourceparams
53 self.system_id = args.system_id
54 self.use_uuid = args.use_uuid
55 self.uuid = args.uuid
56 self.fsuuid = args.fsuuid
57 self.type = args.type
58 self.no_fstab_update = args.no_fstab_update
59 self.updated_fstab_path = None
60 self.has_fstab = False
61 self.update_fstab_in_rootfs = False
62 self.hidden = args.hidden
63 self.mbr = args.mbr
64
65 self.lineno = lineno
66 self.source_file = ""
67
68 def get_extra_block_count(self, current_blocks):
69 """
70 The --size param is reflected in self.size (in kB), and we already
71 have current_blocks (1k) blocks, calculate and return the
72 number of (1k) blocks we need to add to get to --size, 0 if
73 we're already there or beyond.
74 """
75 logger.debug("Requested partition size for %s: %d",
76 self.mountpoint, self.size)
77
78 if not self.size:
79 return 0
80
81 requested_blocks = self.size
82
83 logger.debug("Requested blocks %d, current_blocks %d",
84 requested_blocks, current_blocks)
85
86 if requested_blocks > current_blocks:
87 return requested_blocks - current_blocks
88 else:
89 return 0
90
91 def get_rootfs_size(self, actual_rootfs_size=0):
92 """
93 Calculate the required size of rootfs taking into consideration
94 --size/--fixed-size flags as well as overhead and extra space, as
95 specified in kickstart file. Raises an error if the
96 `actual_rootfs_size` is larger than fixed-size rootfs.
97
98 """
99 if self.fixed_size:
100 rootfs_size = self.fixed_size
101 if actual_rootfs_size > rootfs_size:
102 raise WicError("Actual rootfs size (%d kB) is larger than "
103 "allowed size %d kB" %
104 (actual_rootfs_size, rootfs_size))
105 else:
106 extra_blocks = self.get_extra_block_count(actual_rootfs_size)
107 if extra_blocks < self.extra_space:
108 extra_blocks = self.extra_space
109
110 rootfs_size = actual_rootfs_size + extra_blocks
111 rootfs_size = int(rootfs_size * self.overhead_factor)
112
113 logger.debug("Added %d extra blocks to %s to get to %d total blocks",
114 extra_blocks, self.mountpoint, rootfs_size)
115
116 return rootfs_size
117
118 @property
119 def disk_size(self):
120 """
121 Obtain on-disk size of partition taking into consideration
122 --size/--fixed-size options.
123
124 """
125 return self.fixed_size if self.fixed_size else self.size
126
127 def prepare(self, creator, cr_workdir, oe_builddir, rootfs_dir,
128 bootimg_dir, kernel_dir, native_sysroot, updated_fstab_path):
129 """
130 Prepare content for individual partitions, depending on
131 partition command parameters.
132 """
133 self.updated_fstab_path = updated_fstab_path
134 if self.updated_fstab_path and not (self.fstype.startswith("ext") or self.fstype == "msdos"):
135 self.update_fstab_in_rootfs = True
136
137 if not self.source:
138 if self.fstype == "none" or self.no_table:
139 return
140 if not self.size and not self.fixed_size:
141 raise WicError("The %s partition has a size of zero. Please "
142 "specify a non-zero --size/--fixed-size for that "
143 "partition." % self.mountpoint)
144
145 if self.fstype == "swap":
146 self.prepare_swap_partition(cr_workdir, oe_builddir,
147 native_sysroot)
148 self.source_file = "%s/fs.%s" % (cr_workdir, self.fstype)
149 else:
150 if self.fstype in ('squashfs', 'erofs'):
151 raise WicError("It's not possible to create empty %s "
152 "partition '%s'" % (self.fstype, self.mountpoint))
153
154 rootfs = "%s/fs_%s.%s.%s" % (cr_workdir, self.label,
155 self.lineno, self.fstype)
156 if os.path.isfile(rootfs):
157 os.remove(rootfs)
158
159 prefix = "ext" if self.fstype.startswith("ext") else self.fstype
160 method = getattr(self, "prepare_empty_partition_" + prefix)
161 method(rootfs, oe_builddir, native_sysroot)
162 self.source_file = rootfs
163 return
164
165 plugins = PluginMgr.get_plugins('source')
166
167 if self.source not in plugins:
168 raise WicError("The '%s' --source specified for %s doesn't exist.\n\t"
169 "See 'wic list source-plugins' for a list of available"
170 " --sources.\n\tSee 'wic help source-plugins' for "
171 "details on adding a new source plugin." %
172 (self.source, self.mountpoint))
173
174 srcparams_dict = {}
175 if self.sourceparams:
176 # Split sourceparams string of the form key1=val1[,key2=val2,...]
177 # into a dict. Also accepts valueless keys i.e. without =
178 splitted = self.sourceparams.split(',')
179 srcparams_dict = dict((par.split('=', 1) + [None])[:2] for par in splitted if par)
180
181 plugin = PluginMgr.get_plugins('source')[self.source]
182 plugin.do_configure_partition(self, srcparams_dict, creator,
183 cr_workdir, oe_builddir, bootimg_dir,
184 kernel_dir, native_sysroot)
185 plugin.do_stage_partition(self, srcparams_dict, creator,
186 cr_workdir, oe_builddir, bootimg_dir,
187 kernel_dir, native_sysroot)
188 plugin.do_prepare_partition(self, srcparams_dict, creator,
189 cr_workdir, oe_builddir, bootimg_dir,
190 kernel_dir, rootfs_dir, native_sysroot)
191 plugin.do_post_partition(self, srcparams_dict, creator,
192 cr_workdir, oe_builddir, bootimg_dir,
193 kernel_dir, rootfs_dir, native_sysroot)
194
195 # further processing required Partition.size to be an integer, make
196 # sure that it is one
197 if not isinstance(self.size, int):
198 raise WicError("Partition %s internal size is not an integer. "
199 "This a bug in source plugin %s and needs to be fixed." %
200 (self.mountpoint, self.source))
201
202 if self.fixed_size and self.size > self.fixed_size:
203 raise WicError("File system image of partition %s is "
204 "larger (%d kB) than its allowed size %d kB" %
205 (self.mountpoint, self.size, self.fixed_size))
206
207 def prepare_rootfs(self, cr_workdir, oe_builddir, rootfs_dir,
208 native_sysroot, real_rootfs = True, pseudo_dir = None):
209 """
210 Prepare content for a rootfs partition i.e. create a partition
211 and fill it from a /rootfs dir.
212
213 Currently handles ext2/3/4, btrfs, vfat and squashfs.
214 """
215
216 rootfs = "%s/rootfs_%s.%s.%s" % (cr_workdir, self.label,
217 self.lineno, self.fstype)
218 if os.path.isfile(rootfs):
219 os.remove(rootfs)
220
221 p_prefix = os.environ.get("PSEUDO_PREFIX", "%s/usr" % native_sysroot)
222 if (pseudo_dir):
223 # Canonicalize the ignore paths. This corresponds to
224 # calling oe.path.canonicalize(), which is used in bitbake.conf.
225 ignore_paths = [rootfs] + (get_bitbake_var("PSEUDO_IGNORE_PATHS") or "").split(",")
226 canonical_paths = []
227 for path in ignore_paths:
228 if "$" not in path:
229 trailing_slash = path.endswith("/") and "/" or ""
230 canonical_paths.append(os.path.realpath(path) + trailing_slash)
231 ignore_paths = ",".join(canonical_paths)
232
233 pseudo = "export PSEUDO_PREFIX=%s;" % p_prefix
234 pseudo += "export PSEUDO_LOCALSTATEDIR=%s;" % pseudo_dir
235 pseudo += "export PSEUDO_PASSWD=%s;" % rootfs_dir
236 pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
237 pseudo += "export PSEUDO_IGNORE_PATHS=%s;" % ignore_paths
238 pseudo += "%s " % get_bitbake_var("FAKEROOTCMD")
239 else:
240 pseudo = None
241
242 if not self.size and real_rootfs:
243 # The rootfs size is not set in .ks file so try to get it
244 # from bitbake variable
245 rsize_bb = get_bitbake_var('ROOTFS_SIZE')
246 rdir = get_bitbake_var('IMAGE_ROOTFS')
247 if rsize_bb and rdir == rootfs_dir:
248 # Bitbake variable ROOTFS_SIZE is calculated in
249 # Image._get_rootfs_size method from meta/lib/oe/image.py
250 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
251 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
252 self.size = int(round(float(rsize_bb)))
253 else:
254 # Bitbake variable ROOTFS_SIZE is not defined so compute it
255 # from the rootfs_dir size using the same logic found in
256 # get_rootfs_size() from meta/classes/image.bbclass
257 du_cmd = "du -ks %s" % rootfs_dir
258 out = exec_cmd(du_cmd)
259 self.size = int(out.split()[0])
260
261 prefix = "ext" if self.fstype.startswith("ext") else self.fstype
262 method = getattr(self, "prepare_rootfs_" + prefix)
263 method(rootfs, cr_workdir, oe_builddir, rootfs_dir, native_sysroot, pseudo)
264 self.source_file = rootfs
265
266 # get the rootfs size in the right units for kickstart (kB)
267 du_cmd = "du -Lbks %s" % rootfs
268 out = exec_cmd(du_cmd)
269 self.size = int(out.split()[0])
270
271 def prepare_rootfs_ext(self, rootfs, cr_workdir, oe_builddir, rootfs_dir,
272 native_sysroot, pseudo):
273 """
274 Prepare content for an ext2/3/4 rootfs partition.
275 """
276 du_cmd = "du -ks %s" % rootfs_dir
277 out = exec_cmd(du_cmd)
278 actual_rootfs_size = int(out.split()[0])
279
280 rootfs_size = self.get_rootfs_size(actual_rootfs_size)
281
282 with open(rootfs, 'w') as sparse:
283 os.ftruncate(sparse.fileno(), rootfs_size * 1024)
284
285 extraopts = self.mkfs_extraopts or "-F -i 8192"
286
287 if os.getenv('SOURCE_DATE_EPOCH'):
288 sde_time = int(os.getenv('SOURCE_DATE_EPOCH'))
289 if pseudo:
290 pseudo = "export E2FSPROGS_FAKE_TIME=%s;%s " % (sde_time, pseudo)
291 else:
292 pseudo = "export E2FSPROGS_FAKE_TIME=%s; " % sde_time
293
294 # Set hash_seed to generate deterministic directory indexes
295 namespace = uuid.UUID("e7429877-e7b3-4a68-a5c9-2f2fdf33d460")
296 if self.fsuuid:
297 namespace = uuid.UUID(self.fsuuid)
298 hash_seed = str(uuid.uuid5(namespace, str(sde_time)))
299 extraopts += " -E hash_seed=%s" % hash_seed
300
301 label_str = ""
302 if self.label:
303 label_str = "-L %s" % self.label
304
305 mkfs_cmd = "mkfs.%s %s %s %s -U %s -d %s" % \
306 (self.fstype, extraopts, rootfs, label_str, self.fsuuid, rootfs_dir)
307 exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
308
309 if self.updated_fstab_path and self.has_fstab and not self.no_fstab_update:
310 debugfs_script_path = os.path.join(cr_workdir, "debugfs_script")
311 with open(debugfs_script_path, "w") as f:
312 f.write("cd etc\n")
313 f.write("rm fstab\n")
314 f.write("write %s fstab\n" % (self.updated_fstab_path))
315 debugfs_cmd = "debugfs -w -f %s %s" % (debugfs_script_path, rootfs)
316 exec_native_cmd(debugfs_cmd, native_sysroot)
317
318 mkfs_cmd = "fsck.%s -pvfD %s" % (self.fstype, rootfs)
319 exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
320
321 if os.getenv('SOURCE_DATE_EPOCH'):
322 sde_time = hex(int(os.getenv('SOURCE_DATE_EPOCH')))
323 debugfs_script_path = os.path.join(cr_workdir, "debugfs_script")
324 files = []
325 for root, dirs, others in os.walk(rootfs_dir):
326 base = root.replace(rootfs_dir, "").rstrip(os.sep)
327 files += [ "/" if base == "" else base ]
328 files += [ base + "/" + n for n in dirs + others ]
329 with open(debugfs_script_path, "w") as f:
330 f.write("set_current_time %s\n" % (sde_time))
331 if self.updated_fstab_path and self.has_fstab and not self.no_fstab_update:
332 f.write("set_inode_field /etc/fstab mtime %s\n" % (sde_time))
333 f.write("set_inode_field /etc/fstab mtime_extra 0\n")
334 for file in set(files):
335 for time in ["atime", "ctime", "crtime"]:
336 f.write("set_inode_field \"%s\" %s %s\n" % (file, time, sde_time))
337 f.write("set_inode_field \"%s\" %s_extra 0\n" % (file, time))
338 for time in ["wtime", "mkfs_time", "lastcheck"]:
339 f.write("set_super_value %s %s\n" % (time, sde_time))
340 for time in ["mtime", "first_error_time", "last_error_time"]:
341 f.write("set_super_value %s 0\n" % (time))
342 debugfs_cmd = "debugfs -w -f %s %s" % (debugfs_script_path, rootfs)
343 exec_native_cmd(debugfs_cmd, native_sysroot)
344
345 self.check_for_Y2038_problem(rootfs, native_sysroot)
346
347 def prepare_rootfs_btrfs(self, rootfs, cr_workdir, oe_builddir, rootfs_dir,
348 native_sysroot, pseudo):
349 """
350 Prepare content for a btrfs rootfs partition.
351 """
352 du_cmd = "du -ks %s" % rootfs_dir
353 out = exec_cmd(du_cmd)
354 actual_rootfs_size = int(out.split()[0])
355
356 rootfs_size = self.get_rootfs_size(actual_rootfs_size)
357
358 with open(rootfs, 'w') as sparse:
359 os.ftruncate(sparse.fileno(), rootfs_size * 1024)
360
361 label_str = ""
362 if self.label:
363 label_str = "-L %s" % self.label
364
365 mkfs_cmd = "mkfs.%s -b %d -r %s %s %s -U %s %s" % \
366 (self.fstype, rootfs_size * 1024, rootfs_dir, label_str,
367 self.mkfs_extraopts, self.fsuuid, rootfs)
368 exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
369
370 def prepare_rootfs_msdos(self, rootfs, cr_workdir, oe_builddir, rootfs_dir,
371 native_sysroot, pseudo):
372 """
373 Prepare content for a msdos/vfat rootfs partition.
374 """
375 du_cmd = "du -bks %s" % rootfs_dir
376 out = exec_cmd(du_cmd)
377 blocks = int(out.split()[0])
378
379 rootfs_size = self.get_rootfs_size(blocks)
380
381 label_str = "-n boot"
382 if self.label:
383 label_str = "-n %s" % self.label
384
385 size_str = ""
386
387 extraopts = self.mkfs_extraopts or '-S 512'
388
389 dosfs_cmd = "mkdosfs %s -i %s %s %s -C %s %d" % \
390 (label_str, self.fsuuid, size_str, extraopts, rootfs,
391 rootfs_size)
392 exec_native_cmd(dosfs_cmd, native_sysroot)
393
394 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (rootfs, rootfs_dir)
395 exec_native_cmd(mcopy_cmd, native_sysroot)
396
397 if self.updated_fstab_path and self.has_fstab and not self.no_fstab_update:
398 mcopy_cmd = "mcopy -m -i %s %s ::/etc/fstab" % (rootfs, self.updated_fstab_path)
399 exec_native_cmd(mcopy_cmd, native_sysroot)
400
401 chmod_cmd = "chmod 644 %s" % rootfs
402 exec_cmd(chmod_cmd)
403
404 prepare_rootfs_vfat = prepare_rootfs_msdos
405
406 def prepare_rootfs_squashfs(self, rootfs, cr_workdir, oe_builddir, rootfs_dir,
407 native_sysroot, pseudo):
408 """
409 Prepare content for a squashfs rootfs partition.
410 """
411 extraopts = self.mkfs_extraopts or '-noappend'
412 squashfs_cmd = "mksquashfs %s %s %s" % \
413 (rootfs_dir, rootfs, extraopts)
414 exec_native_cmd(squashfs_cmd, native_sysroot, pseudo=pseudo)
415
416 def prepare_rootfs_erofs(self, rootfs, cr_workdir, oe_builddir, rootfs_dir,
417 native_sysroot, pseudo):
418 """
419 Prepare content for a erofs rootfs partition.
420 """
421 extraopts = self.mkfs_extraopts or ''
422 erofs_cmd = "mkfs.erofs %s -U %s %s %s" % \
423 (extraopts, self.fsuuid, rootfs, rootfs_dir)
424 exec_native_cmd(erofs_cmd, native_sysroot, pseudo=pseudo)
425
426 def prepare_empty_partition_none(self, rootfs, oe_builddir, native_sysroot):
427 pass
428
429 def prepare_empty_partition_ext(self, rootfs, oe_builddir,
430 native_sysroot):
431 """
432 Prepare an empty ext2/3/4 partition.
433 """
434 size = self.disk_size
435 with open(rootfs, 'w') as sparse:
436 os.ftruncate(sparse.fileno(), size * 1024)
437
438 extraopts = self.mkfs_extraopts or "-i 8192"
439
440 label_str = ""
441 if self.label:
442 label_str = "-L %s" % self.label
443
444 mkfs_cmd = "mkfs.%s -F %s %s -U %s %s" % \
445 (self.fstype, extraopts, label_str, self.fsuuid, rootfs)
446 exec_native_cmd(mkfs_cmd, native_sysroot)
447
448 self.check_for_Y2038_problem(rootfs, native_sysroot)
449
450 def prepare_empty_partition_btrfs(self, rootfs, oe_builddir,
451 native_sysroot):
452 """
453 Prepare an empty btrfs partition.
454 """
455 size = self.disk_size
456 with open(rootfs, 'w') as sparse:
457 os.ftruncate(sparse.fileno(), size * 1024)
458
459 label_str = ""
460 if self.label:
461 label_str = "-L %s" % self.label
462
463 mkfs_cmd = "mkfs.%s -b %d %s -U %s %s %s" % \
464 (self.fstype, self.size * 1024, label_str, self.fsuuid,
465 self.mkfs_extraopts, rootfs)
466 exec_native_cmd(mkfs_cmd, native_sysroot)
467
468 def prepare_empty_partition_msdos(self, rootfs, oe_builddir,
469 native_sysroot):
470 """
471 Prepare an empty vfat partition.
472 """
473 blocks = self.disk_size
474
475 label_str = "-n boot"
476 if self.label:
477 label_str = "-n %s" % self.label
478
479 size_str = ""
480
481 extraopts = self.mkfs_extraopts or '-S 512'
482
483 dosfs_cmd = "mkdosfs %s -i %s %s %s -C %s %d" % \
484 (label_str, self.fsuuid, extraopts, size_str, rootfs,
485 blocks)
486
487 exec_native_cmd(dosfs_cmd, native_sysroot)
488
489 chmod_cmd = "chmod 644 %s" % rootfs
490 exec_cmd(chmod_cmd)
491
492 prepare_empty_partition_vfat = prepare_empty_partition_msdos
493
494 def prepare_swap_partition(self, cr_workdir, oe_builddir, native_sysroot):
495 """
496 Prepare a swap partition.
497 """
498 path = "%s/fs.%s" % (cr_workdir, self.fstype)
499
500 with open(path, 'w') as sparse:
501 os.ftruncate(sparse.fileno(), self.size * 1024)
502
503 label_str = ""
504 if self.label:
505 label_str = "-L %s" % self.label
506
507 mkswap_cmd = "mkswap %s -U %s %s" % (label_str, self.fsuuid, path)
508 exec_native_cmd(mkswap_cmd, native_sysroot)
509
510 def check_for_Y2038_problem(self, rootfs, native_sysroot):
511 """
512 Check if the filesystem is affected by the Y2038 problem
513 (Y2038 problem = 32 bit time_t overflow in January 2038)
514 """
515 def get_err_str(part):
516 err = "The {} filesystem {} has no Y2038 support."
517 if part.mountpoint:
518 args = [part.fstype, "mounted at %s" % part.mountpoint]
519 elif part.label:
520 args = [part.fstype, "labeled '%s'" % part.label]
521 elif part.part_name:
522 args = [part.fstype, "in partition '%s'" % part.part_name]
523 else:
524 args = [part.fstype, "in partition %s" % part.num]
525 return err.format(*args)
526
527 # ext2 and ext3 are always affected by the Y2038 problem
528 if self.fstype in ["ext2", "ext3"]:
529 logger.warn(get_err_str(self))
530 return
531
532 ret, out = exec_native_cmd("dumpe2fs %s" % rootfs, native_sysroot)
533
534 # if ext4 is affected by the Y2038 problem depends on the inode size
535 for line in out.splitlines():
536 if line.startswith("Inode size:"):
537 size = int(line.split(":")[1].strip())
538 if size < 256:
539 logger.warn("%s Inodes (of size %d) are too small." %
540 (get_err_str(self), size))
541 break
542
diff --git a/scripts/lib/wic/pluginbase.py b/scripts/lib/wic/pluginbase.py
deleted file mode 100644
index b64568339b..0000000000
--- a/scripts/lib/wic/pluginbase.py
+++ /dev/null
@@ -1,144 +0,0 @@
1#!/usr/bin/env python3
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8__all__ = ['ImagerPlugin', 'SourcePlugin']
9
10import os
11import logging
12import types
13
14from collections import defaultdict
15import importlib
16import importlib.util
17
18from wic import WicError
19from wic.misc import get_bitbake_var
20
21PLUGIN_TYPES = ["imager", "source"]
22
23SCRIPTS_PLUGIN_DIR = ["scripts/lib/wic/plugins", "lib/wic/plugins"]
24
25logger = logging.getLogger('wic')
26
27PLUGINS = defaultdict(dict)
28
29class PluginMgr:
30 _plugin_dirs = []
31
32 @classmethod
33 def get_plugins(cls, ptype):
34 """Get dictionary of <plugin_name>:<class> pairs."""
35 if ptype not in PLUGIN_TYPES:
36 raise WicError('%s is not valid plugin type' % ptype)
37
38 # collect plugin directories
39 if not cls._plugin_dirs:
40 cls._plugin_dirs = [os.path.join(os.path.dirname(__file__), 'plugins')]
41 layers = get_bitbake_var("BBLAYERS") or ''
42 for layer_path in layers.split():
43 for script_plugin_dir in SCRIPTS_PLUGIN_DIR:
44 path = os.path.join(layer_path, script_plugin_dir)
45 path = os.path.abspath(os.path.expanduser(path))
46 if path not in cls._plugin_dirs and os.path.isdir(path):
47 cls._plugin_dirs.insert(0, path)
48
49 if ptype not in PLUGINS:
50 # load all ptype plugins
51 for pdir in cls._plugin_dirs:
52 ppath = os.path.join(pdir, ptype)
53 if os.path.isdir(ppath):
54 for fname in os.listdir(ppath):
55 if fname.endswith('.py'):
56 mname = fname[:-3]
57 mpath = os.path.join(ppath, fname)
58 logger.debug("loading plugin module %s", mpath)
59 spec = importlib.util.spec_from_file_location(mname, mpath)
60 module = importlib.util.module_from_spec(spec)
61 spec.loader.exec_module(module)
62
63 return PLUGINS.get(ptype)
64
65class PluginMeta(type):
66 def __new__(cls, name, bases, attrs):
67 class_type = type.__new__(cls, name, bases, attrs)
68 if 'name' in attrs:
69 PLUGINS[class_type.wic_plugin_type][attrs['name']] = class_type
70
71 return class_type
72
73class ImagerPlugin(metaclass=PluginMeta):
74 wic_plugin_type = "imager"
75
76 def do_create(self):
77 raise WicError("Method %s.do_create is not implemented" %
78 self.__class__.__name__)
79
80class SourcePlugin(metaclass=PluginMeta):
81 wic_plugin_type = "source"
82 """
83 The methods that can be implemented by --source plugins.
84
85 Any methods not implemented in a subclass inherit these.
86 """
87
88 @classmethod
89 def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
90 bootimg_dir, kernel_dir, native_sysroot):
91 """
92 Called after all partitions have been prepared and assembled into a
93 disk image. This provides a hook to allow finalization of a
94 disk image e.g. to write an MBR to it.
95 """
96 logger.debug("SourcePlugin: do_install_disk: disk: %s", disk_name)
97
98 @classmethod
99 def do_stage_partition(cls, part, source_params, creator, cr_workdir,
100 oe_builddir, bootimg_dir, kernel_dir,
101 native_sysroot):
102 """
103 Special content staging hook called before do_prepare_partition(),
104 normally empty.
105
106 Typically, a partition will just use the passed-in parame e.g
107 straight bootimg_dir, etc, but in some cases, things need to
108 be more tailored e.g. to use a deploy dir + /boot, etc. This
109 hook allows those files to be staged in a customized fashion.
110 Not that get_bitbake_var() allows you to acces non-standard
111 variables that you might want to use for this.
112 """
113 logger.debug("SourcePlugin: do_stage_partition: part: %s", part)
114
115 @classmethod
116 def do_configure_partition(cls, part, source_params, creator, cr_workdir,
117 oe_builddir, bootimg_dir, kernel_dir,
118 native_sysroot):
119 """
120 Called before do_prepare_partition(), typically used to create
121 custom configuration files for a partition, for example
122 syslinux or grub config files.
123 """
124 logger.debug("SourcePlugin: do_configure_partition: part: %s", part)
125
126 @classmethod
127 def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
128 oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
129 native_sysroot):
130 """
131 Called to do the actual content population for a partition i.e. it
132 'prepares' the partition to be incorporated into the image.
133 """
134 logger.debug("SourcePlugin: do_prepare_partition: part: %s", part)
135
136 @classmethod
137 def do_post_partition(cls, part, source_params, creator, cr_workdir,
138 oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
139 native_sysroot):
140 """
141 Called after the partition is created. It is useful to add post
142 operations e.g. security signing the partition.
143 """
144 logger.debug("SourcePlugin: do_post_partition: part: %s", part)
diff --git a/scripts/lib/wic/plugins/imager/direct.py b/scripts/lib/wic/plugins/imager/direct.py
deleted file mode 100644
index a1d152659b..0000000000
--- a/scripts/lib/wic/plugins/imager/direct.py
+++ /dev/null
@@ -1,694 +0,0 @@
1#
2# Copyright (c) 2013, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
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
15import random
16import shutil
17import tempfile
18import uuid
19
20from time import strftime
21
22from oe.path import copyhardlinktree
23
24from wic import WicError
25from wic.filemap import sparse_copy
26from wic.ksparser import KickStart, KickStartError
27from wic.pluginbase import PluginMgr, ImagerPlugin
28from wic.misc import get_bitbake_var, exec_cmd, exec_native_cmd
29
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
57 self.debug = options.debug
58 self.outdir = options.outdir
59 self.compressor = options.compressor
60 self.bmap = options.bmap
61 self.no_fstab_update = options.no_fstab_update
62 self.updated_fstab_path = None
63
64 self.name = "%s-%s" % (os.path.splitext(os.path.basename(wks_file))[0],
65 strftime("%Y%m%d%H%M"))
66 self.workdir = self.setup_workdir(options.workdir)
67 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,
80 self.parts, self.native_sysroot,
81 options.extra_space)
82
83 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
93 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
105 def update_fstab(self, image_rootfs):
106 """Assume partition order same as in wks"""
107 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
117 updated = False
118 for part in self.parts:
119 if not part.realnum or not part.mountpoint \
120 or part.mountpoint == "/" or not (part.mountpoint.startswith('/') or part.mountpoint == "swap"):
121 continue
122
123 if part.use_uuid:
124 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
133 elif part.use_label:
134 device_name = "LABEL=%s" % part.label
135 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)
139
140 opts = part.fsopts if part.fsopts else "defaults"
141 passno = part.fspassno if part.fspassno else "0"
142 line = "\t".join([device_name, part.mountpoint, part.fstype,
143 opts, "0", passno]) + "\n"
144
145 fstab_lines.append(line)
146 updated = True
147
148 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)
152 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))
155
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 """
169 if not self.no_fstab_update:
170 self.update_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
171
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)
187 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)
215 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)
219 # 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)
245 rootdir = part.rootfs_dir
246 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), rootdir)
247
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
266 elif part.label and self.ptable_format != 'msdos':
267 return "PARTLABEL=%s" % part.label
268 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
285 # remove work directory when it is not in debugging mode
286 if not self.debug:
287 shutil.rmtree(self.workdir, ignore_errors=True)
288
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
303 def __init__(self, path, ptable_format, partitions, native_sysroot=None, extra_space=0):
304 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
307 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)
311 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
316 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)
320
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
326 num_real_partitions = len([p for p in self.partitions if not p.no_table])
327 self.extra_space = extra_space
328
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
337 if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4:
338 part.realnum = realnum + 1
339 continue
340 part.realnum = realnum
341
342 # generate parition and filesystem UUIDs
343 for part in self.partitions:
344 if not part.uuid and part.use_uuid:
345 if self.ptable_format in ('gpt', 'gpt-hybrid'):
346 part.uuid = str(uuid.uuid4())
347 else: # msdos partition table
348 part.uuid = '%08x-%02d' % (self.identifier, part.realnum)
349 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())
354 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")
361
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,
369 imager.kernel_dir, imager.native_sysroot,
370 imager.updated_fstab_path)
371
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
390 if self.ptable_format == 'msdos' and part.part_name:
391 raise WicError("setting custom partition name is not " \
392 "implemented for msdos partitions")
393
394 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 if part.mbr and self.ptable_format != 'gpt-hybrid':
402 raise WicError("Partition may only be included in MBR with " \
403 "a gpt-hybrid partition table")
404
405 # Get the disk where the partition is located
406 self.numpart += 1
407 if not part.no_table:
408 self.realpart += 1
409
410 if self.numpart == 1:
411 if self.ptable_format == "msdos":
412 overhead = MBR_OVERHEAD
413 elif self.ptable_format in ("gpt", "gpt-hybrid"):
414 overhead = GPT_OVERHEAD
415
416 # Skip one sector required for the partitioning scheme overhead
417 self.offset += overhead
418
419 if self.ptable_format == "msdos":
420 if self.primary_part_num > 3 or \
421 (self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4):
422 part.type = 'logical'
423 # Reserve a sector for EBR for every logical partition
424 # before alignment is performed.
425 if part.type == 'logical':
426 self.offset += 2
427
428 align_sectors = 0
429 if part.align:
430 # If not first partition and we do have alignment set we need
431 # to align the partition.
432 # FIXME: This leaves a empty spaces to the disk. To fill the
433 # gaps we could enlargea the previous partition?
434
435 # Calc how much the alignment is off.
436 align_sectors = self.offset % (part.align * 1024 // self.sector_size)
437
438 if align_sectors:
439 # If partition is not aligned as required, we need
440 # to move forward to the next alignment point
441 align_sectors = (part.align * 1024 // self.sector_size) - align_sectors
442
443 logger.debug("Realignment for %s%s with %s sectors, original"
444 " offset %s, target alignment is %sK.",
445 part.disk, self.numpart, align_sectors,
446 self.offset, part.align)
447
448 # increase the offset so we actually start the partition on right alignment
449 self.offset += align_sectors
450
451 if part.offset is not None:
452 offset = part.offset // self.sector_size
453
454 if offset * self.sector_size != part.offset:
455 raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size))
456
457 delta = offset - self.offset
458 if delta < 0:
459 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))
460
461 logger.debug("Skipping %d sectors to place %s%s at offset %dK",
462 delta, part.disk, self.numpart, part.offset)
463
464 self.offset = offset
465
466 part.start = self.offset
467 self.offset += part.size_sec
468
469 if not part.no_table:
470 part.num = self.realpart
471 else:
472 part.num = 0
473
474 if self.ptable_format == "msdos" and not part.no_table:
475 if part.type == 'logical':
476 self.logical_part_cnt += 1
477 part.num = self.logical_part_cnt + 4
478 if self.extendedpart == 0:
479 # Create extended partition as a primary partition
480 self.primary_part_num += 1
481 self.extendedpart = part.num
482 else:
483 self.extended_size_sec += align_sectors
484 self.extended_size_sec += part.size_sec + 2
485 else:
486 self.primary_part_num += 1
487 part.num = self.primary_part_num
488
489 logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
490 "sectors (%d bytes).", part.mountpoint, part.disk,
491 part.num, part.start, self.offset - 1, part.size_sec,
492 part.size_sec * self.sector_size)
493
494 # Once all the partitions have been layed out, we can calculate the
495 # minumim disk size
496 self.min_size = self.offset
497 if self.ptable_format in ("gpt", "gpt-hybrid"):
498 self.min_size += GPT_OVERHEAD
499
500 self.min_size *= self.sector_size
501 self.min_size += self.extra_space
502
503 def _create_partition(self, device, parttype, fstype, start, size):
504 """ Create a partition on an image described by the 'device' object. """
505
506 # Start is included to the size so we need to substract one from the end.
507 end = start + size - 1
508 logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors",
509 parttype, start, end, size)
510
511 cmd = "parted -s %s unit s mkpart %s" % (device, parttype)
512 if fstype:
513 cmd += " %s" % fstype
514 cmd += " %d %d" % (start, end)
515
516 return exec_native_cmd(cmd, self.native_sysroot)
517
518 def _write_identifier(self, device, identifier):
519 logger.debug("Set disk identifier %x", identifier)
520 with open(device, 'r+b') as img:
521 img.seek(0x1B8)
522 img.write(identifier.to_bytes(4, 'little'))
523
524 def _make_disk(self, device, ptable_format, min_size):
525 logger.debug("Creating sparse file %s", device)
526 with open(device, 'w') as sparse:
527 os.ftruncate(sparse.fileno(), min_size)
528
529 logger.debug("Initializing partition table for %s", device)
530 exec_native_cmd("parted -s %s mklabel %s" % (device, ptable_format),
531 self.native_sysroot)
532
533 def _write_disk_guid(self):
534 if self.ptable_format in ('gpt', 'gpt-hybrid'):
535 if os.getenv('SOURCE_DATE_EPOCH'):
536 self.disk_guid = uuid.UUID(int=int(os.getenv('SOURCE_DATE_EPOCH')))
537 else:
538 self.disk_guid = uuid.uuid4()
539
540 logger.debug("Set disk guid %s", self.disk_guid)
541 sfdisk_cmd = "sfdisk --disk-id %s %s" % (self.path, self.disk_guid)
542 exec_native_cmd(sfdisk_cmd, self.native_sysroot)
543
544 def create(self):
545 self._make_disk(self.path,
546 "gpt" if self.ptable_format == "gpt-hybrid" else self.ptable_format,
547 self.min_size)
548
549 self._write_identifier(self.path, self.identifier)
550 self._write_disk_guid()
551
552 if self.ptable_format == "gpt-hybrid":
553 mbr_path = self.path + ".mbr"
554 self._make_disk(mbr_path, "msdos", self.min_size)
555 self._write_identifier(mbr_path, self.identifier)
556
557 logger.debug("Creating partitions")
558
559 hybrid_mbr_part_num = 0
560
561 for part in self.partitions:
562 if part.num == 0:
563 continue
564
565 if self.ptable_format == "msdos" and part.num == self.extendedpart:
566 # Create an extended partition (note: extended
567 # partition is described in MBR and contains all
568 # logical partitions). The logical partitions save a
569 # sector for an EBR just before the start of a
570 # partition. The extended partition must start one
571 # sector before the start of the first logical
572 # partition. This way the first EBR is inside of the
573 # extended partition. Since the extended partitions
574 # starts a sector before the first logical partition,
575 # add a sector at the back, so that there is enough
576 # room for all logical partitions.
577 self._create_partition(self.path, "extended",
578 None, part.start - 2,
579 self.extended_size_sec)
580
581 if part.fstype == "swap":
582 parted_fs_type = "linux-swap"
583 elif part.fstype == "vfat":
584 parted_fs_type = "fat32"
585 elif part.fstype == "msdos":
586 parted_fs_type = "fat16"
587 if not part.system_id:
588 part.system_id = '0x6' # FAT16
589 else:
590 # Type for ext2/ext3/ext4/btrfs
591 parted_fs_type = "ext2"
592
593 # Boot ROM of OMAP boards require vfat boot partition to have an
594 # even number of sectors.
595 if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \
596 and part.size_sec % 2:
597 logger.debug("Subtracting one sector from '%s' partition to "
598 "get even number of sectors for the partition",
599 part.mountpoint)
600 part.size_sec -= 1
601
602 self._create_partition(self.path, part.type,
603 parted_fs_type, part.start, part.size_sec)
604
605 if self.ptable_format == "gpt-hybrid" and part.mbr:
606 hybrid_mbr_part_num += 1
607 if hybrid_mbr_part_num > 4:
608 raise WicError("Extended MBR partitions are not supported in hybrid MBR")
609 self._create_partition(mbr_path, "primary",
610 parted_fs_type, part.start, part.size_sec)
611
612 if self.ptable_format in ("gpt", "gpt-hybrid") and (part.part_name or part.label):
613 partition_label = part.part_name if part.part_name else part.label
614 logger.debug("partition %d: set name to %s",
615 part.num, partition_label)
616 exec_native_cmd("sgdisk --change-name=%d:%s %s" % \
617 (part.num, partition_label,
618 self.path), self.native_sysroot)
619
620 if part.part_type:
621 logger.debug("partition %d: set type UID to %s",
622 part.num, part.part_type)
623 exec_native_cmd("sgdisk --typecode=%d:%s %s" % \
624 (part.num, part.part_type,
625 self.path), self.native_sysroot)
626
627 if part.uuid and self.ptable_format in ("gpt", "gpt-hybrid"):
628 logger.debug("partition %d: set UUID to %s",
629 part.num, part.uuid)
630 exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \
631 (part.num, part.uuid, self.path),
632 self.native_sysroot)
633
634 if part.active:
635 flag_name = "legacy_boot" if self.ptable_format in ('gpt', 'gpt-hybrid') else "boot"
636 logger.debug("Set '%s' flag for partition '%s' on disk '%s'",
637 flag_name, part.num, self.path)
638 exec_native_cmd("parted -s %s set %d %s on" % \
639 (self.path, part.num, flag_name),
640 self.native_sysroot)
641 if self.ptable_format == 'gpt-hybrid' and part.mbr:
642 exec_native_cmd("parted -s %s set %d %s on" % \
643 (mbr_path, hybrid_mbr_part_num, "boot"),
644 self.native_sysroot)
645 if part.system_id:
646 exec_native_cmd("sfdisk --part-type %s %s %s" % \
647 (self.path, part.num, part.system_id),
648 self.native_sysroot)
649
650 if part.hidden and self.ptable_format == "gpt":
651 logger.debug("Set hidden attribute for partition '%s' on disk '%s'",
652 part.num, self.path)
653 exec_native_cmd("sfdisk --part-attrs %s %s RequiredPartition" % \
654 (self.path, part.num),
655 self.native_sysroot)
656
657 if self.ptable_format == "gpt-hybrid":
658 # Write a protective GPT partition
659 hybrid_mbr_part_num += 1
660 if hybrid_mbr_part_num > 4:
661 raise WicError("Extended MBR partitions are not supported in hybrid MBR")
662
663 # parted cannot directly create a protective GPT partition, so
664 # create with an arbitrary type, then change it to the correct type
665 # with sfdisk
666 self._create_partition(mbr_path, "primary", "fat32", 1, GPT_OVERHEAD)
667 exec_native_cmd("sfdisk --part-type %s %d 0xee" % (mbr_path, hybrid_mbr_part_num),
668 self.native_sysroot)
669
670 # Copy hybrid MBR
671 with open(mbr_path, "rb") as mbr_file:
672 with open(self.path, "r+b") as image_file:
673 mbr = mbr_file.read(512)
674 image_file.write(mbr)
675
676 def cleanup(self):
677 pass
678
679 def assemble(self):
680 logger.debug("Installing partitions")
681
682 for part in self.partitions:
683 source = part.source_file
684 if source:
685 # install source_file contents into a partition
686 sparse_copy(source, self.path, seek=part.start * self.sector_size)
687
688 logger.debug("Installed %s in partition %d, sectors %d-%d, "
689 "size %d sectors", source, part.num, part.start,
690 part.start + part.size_sec - 1, part.size_sec)
691
692 partimage = self.path + '.p%d' % part.num
693 os.rename(source, partimage)
694 self.partimages.append(partimage)
diff --git a/scripts/lib/wic/plugins/source/bootimg-biosplusefi.py b/scripts/lib/wic/plugins/source/bootimg-biosplusefi.py
deleted file mode 100644
index 5bd7390680..0000000000
--- a/scripts/lib/wic/plugins/source/bootimg-biosplusefi.py
+++ /dev/null
@@ -1,213 +0,0 @@
1#
2# This program is free software; you can redistribute it and/or modify
3# it under the terms of the GNU General Public License version 2 as
4# published by the Free Software Foundation.
5#
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU General Public License for more details.
10#
11# You should have received a copy of the GNU General Public License along
12# with this program; if not, write to the Free Software Foundation, Inc.,
13# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
14#
15# DESCRIPTION
16# This implements the 'bootimg-biosplusefi' source plugin class for 'wic'
17#
18# AUTHORS
19# William Bourque <wbourque [at) gmail.com>
20
21import types
22
23from wic.pluginbase import SourcePlugin
24from importlib.machinery import SourceFileLoader
25
26class BootimgBiosPlusEFIPlugin(SourcePlugin):
27 """
28 Create MBR + EFI boot partition
29
30 This plugin creates a boot partition that contains both
31 legacy BIOS and EFI content. It will be able to boot from both.
32 This is useful when managing PC fleet with some older machines
33 without EFI support.
34
35 Note it is possible to create an image that can boot from both
36 legacy BIOS and EFI by defining two partitions : one with arg
37 --source bootimg-efi and another one with --source bootimg-pcbios.
38 However, this method has the obvious downside that it requires TWO
39 partitions to be created on the storage device.
40 Both partitions will also be marked as "bootable" which does not work on
41 most BIOS, has BIOS often uses the "bootable" flag to determine
42 what to boot. If you have such a BIOS, you need to manually remove the
43 "bootable" flag from the EFI partition for the drive to be bootable.
44 Having two partitions also seems to confuse wic : the content of
45 the first partition will be duplicated into the second, even though it
46 will not be used at all.
47
48 Also, unlike "isoimage-isohybrid" that also does BIOS and EFI, this plugin
49 allows you to have more than only a single rootfs partitions and does
50 not turn the rootfs into an initramfs RAM image.
51
52 This plugin is made to put everything into a single /boot partition so it
53 does not have the limitations listed above.
54
55 The plugin is made so it does tries not to reimplement what's already
56 been done in other plugins; as such it imports "bootimg-pcbios"
57 and "bootimg-efi".
58 Plugin "bootimg-pcbios" is used to generate legacy BIOS boot.
59 Plugin "bootimg-efi" is used to generate the UEFI boot. Note that it
60 requires a --sourceparams argument to know which loader to use; refer
61 to "bootimg-efi" code/documentation for the list of loader.
62
63 Imports are handled with "SourceFileLoader" from importlib as it is
64 otherwise very difficult to import module that has hyphen "-" in their
65 filename.
66 The SourcePlugin() methods used in the plugins (do_install_disk,
67 do_configure_partition, do_prepare_partition) are then called on both,
68 beginning by "bootimg-efi".
69
70 Plugin options, such as "--sourceparams" can still be passed to a
71 plugin, as long they does not cause issue in the other plugin.
72
73 Example wic configuration:
74 part /boot --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\\
75 --ondisk sda --label os_boot --active --align 1024 --use-uuid
76 """
77
78 name = 'bootimg-biosplusefi'
79
80 __PCBIOS_MODULE_NAME = "bootimg-pcbios"
81 __EFI_MODULE_NAME = "bootimg-efi"
82
83 __imgEFIObj = None
84 __imgBiosObj = None
85
86 @classmethod
87 def __init__(cls):
88 """
89 Constructor (init)
90 """
91
92 # XXX
93 # For some reasons, __init__ constructor is never called.
94 # Something to do with how pluginbase works?
95 cls.__instanciateSubClasses()
96
97 @classmethod
98 def __instanciateSubClasses(cls):
99 """
100
101 """
102
103 # Import bootimg-pcbios (class name "BootimgPcbiosPlugin")
104 modulePath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
105 cls.__PCBIOS_MODULE_NAME + ".py")
106 loader = SourceFileLoader(cls.__PCBIOS_MODULE_NAME, modulePath)
107 mod = types.ModuleType(loader.name)
108 loader.exec_module(mod)
109 cls.__imgBiosObj = mod.BootimgPcbiosPlugin()
110
111 # Import bootimg-efi (class name "BootimgEFIPlugin")
112 modulePath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
113 cls.__EFI_MODULE_NAME + ".py")
114 loader = SourceFileLoader(cls.__EFI_MODULE_NAME, modulePath)
115 mod = types.ModuleType(loader.name)
116 loader.exec_module(mod)
117 cls.__imgEFIObj = mod.BootimgEFIPlugin()
118
119 @classmethod
120 def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
121 bootimg_dir, kernel_dir, native_sysroot):
122 """
123 Called after all partitions have been prepared and assembled into a
124 disk image.
125 """
126
127 if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
128 cls.__instanciateSubClasses()
129
130 cls.__imgEFIObj.do_install_disk(
131 disk,
132 disk_name,
133 creator,
134 workdir,
135 oe_builddir,
136 bootimg_dir,
137 kernel_dir,
138 native_sysroot)
139
140 cls.__imgBiosObj.do_install_disk(
141 disk,
142 disk_name,
143 creator,
144 workdir,
145 oe_builddir,
146 bootimg_dir,
147 kernel_dir,
148 native_sysroot)
149
150 @classmethod
151 def do_configure_partition(cls, part, source_params, creator, cr_workdir,
152 oe_builddir, bootimg_dir, kernel_dir,
153 native_sysroot):
154 """
155 Called before do_prepare_partition()
156 """
157
158 if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
159 cls.__instanciateSubClasses()
160
161 cls.__imgEFIObj.do_configure_partition(
162 part,
163 source_params,
164 creator,
165 cr_workdir,
166 oe_builddir,
167 bootimg_dir,
168 kernel_dir,
169 native_sysroot)
170
171 cls.__imgBiosObj.do_configure_partition(
172 part,
173 source_params,
174 creator,
175 cr_workdir,
176 oe_builddir,
177 bootimg_dir,
178 kernel_dir,
179 native_sysroot)
180
181 @classmethod
182 def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
183 oe_builddir, bootimg_dir, kernel_dir,
184 rootfs_dir, native_sysroot):
185 """
186 Called to do the actual content population for a partition i.e. it
187 'prepares' the partition to be incorporated into the image.
188 """
189
190 if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
191 cls.__instanciateSubClasses()
192
193 cls.__imgEFIObj.do_prepare_partition(
194 part,
195 source_params,
196 creator,
197 cr_workdir,
198 oe_builddir,
199 bootimg_dir,
200 kernel_dir,
201 rootfs_dir,
202 native_sysroot)
203
204 cls.__imgBiosObj.do_prepare_partition(
205 part,
206 source_params,
207 creator,
208 cr_workdir,
209 oe_builddir,
210 bootimg_dir,
211 kernel_dir,
212 rootfs_dir,
213 native_sysroot)
diff --git a/scripts/lib/wic/plugins/source/bootimg-efi.py b/scripts/lib/wic/plugins/source/bootimg-efi.py
deleted file mode 100644
index 13a9cddf4e..0000000000
--- a/scripts/lib/wic/plugins/source/bootimg-efi.py
+++ /dev/null
@@ -1,507 +0,0 @@
1#
2# Copyright (c) 2014, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'bootimg-efi' source plugin class for 'wic'
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11#
12
13import logging
14import os
15import tempfile
16import shutil
17import re
18
19from glob import glob
20
21from wic import WicError
22from wic.engine import get_custom_config
23from wic.pluginbase import SourcePlugin
24from wic.misc import (exec_cmd, exec_native_cmd,
25 get_bitbake_var, BOOTDD_EXTRA_SPACE)
26
27logger = logging.getLogger('wic')
28
29class BootimgEFIPlugin(SourcePlugin):
30 """
31 Create EFI boot partition.
32 This plugin supports GRUB 2 and systemd-boot bootloaders.
33 """
34
35 name = 'bootimg-efi'
36
37 @classmethod
38 def _copy_additional_files(cls, hdddir, initrd, dtb):
39 bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
40 if not bootimg_dir:
41 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
42
43 if initrd:
44 initrds = initrd.split(';')
45 for rd in initrds:
46 cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
47 exec_cmd(cp_cmd, True)
48 else:
49 logger.debug("Ignoring missing initrd")
50
51 if dtb:
52 if ';' in dtb:
53 raise WicError("Only one DTB supported, exiting")
54 cp_cmd = "cp %s/%s %s" % (bootimg_dir, dtb, hdddir)
55 exec_cmd(cp_cmd, True)
56
57 @classmethod
58 def do_configure_grubefi(cls, hdddir, creator, cr_workdir, source_params):
59 """
60 Create loader-specific (grub-efi) config
61 """
62 configfile = creator.ks.bootloader.configfile
63 custom_cfg = None
64 if configfile:
65 custom_cfg = get_custom_config(configfile)
66 if custom_cfg:
67 # Use a custom configuration for grub
68 grubefi_conf = custom_cfg
69 logger.debug("Using custom configuration file "
70 "%s for grub.cfg", configfile)
71 else:
72 raise WicError("configfile is specified but failed to "
73 "get it from %s." % configfile)
74
75 initrd = source_params.get('initrd')
76 dtb = source_params.get('dtb')
77
78 cls._copy_additional_files(hdddir, initrd, dtb)
79
80 if not custom_cfg:
81 # Create grub configuration using parameters from wks file
82 bootloader = creator.ks.bootloader
83 title = source_params.get('title')
84
85 grubefi_conf = ""
86 grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
87 grubefi_conf += "default=boot\n"
88 grubefi_conf += "timeout=%s\n" % bootloader.timeout
89 grubefi_conf += "menuentry '%s'{\n" % (title if title else "boot")
90
91 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
92 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
93 if get_bitbake_var("INITRAMFS_IMAGE"):
94 kernel = "%s-%s.bin" % \
95 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
96
97 label = source_params.get('label')
98 label_conf = "root=%s" % creator.rootdev
99 if label:
100 label_conf = "LABEL=%s" % label
101
102 grubefi_conf += "linux /%s %s rootwait %s\n" \
103 % (kernel, label_conf, bootloader.append)
104
105 if initrd:
106 initrds = initrd.split(';')
107 grubefi_conf += "initrd"
108 for rd in initrds:
109 grubefi_conf += " /%s" % rd
110 grubefi_conf += "\n"
111
112 if dtb:
113 grubefi_conf += "devicetree /%s\n" % dtb
114
115 grubefi_conf += "}\n"
116
117 logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
118 cr_workdir)
119 cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
120 cfg.write(grubefi_conf)
121 cfg.close()
122
123 @classmethod
124 def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
125 """
126 Create loader-specific systemd-boot/gummiboot config
127 """
128 install_cmd = "install -d %s/loader" % hdddir
129 exec_cmd(install_cmd)
130
131 install_cmd = "install -d %s/loader/entries" % hdddir
132 exec_cmd(install_cmd)
133
134 bootloader = creator.ks.bootloader
135
136 unified_image = source_params.get('create-unified-kernel-image') == "true"
137
138 loader_conf = ""
139 if not unified_image:
140 loader_conf += "default boot\n"
141 loader_conf += "timeout %d\n" % bootloader.timeout
142
143 initrd = source_params.get('initrd')
144 dtb = source_params.get('dtb')
145
146 if not unified_image:
147 cls._copy_additional_files(hdddir, initrd, dtb)
148
149 logger.debug("Writing systemd-boot config "
150 "%s/hdd/boot/loader/loader.conf", cr_workdir)
151 cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
152 cfg.write(loader_conf)
153 cfg.close()
154
155 configfile = creator.ks.bootloader.configfile
156 custom_cfg = None
157 if configfile:
158 custom_cfg = get_custom_config(configfile)
159 if custom_cfg:
160 # Use a custom configuration for systemd-boot
161 boot_conf = custom_cfg
162 logger.debug("Using custom configuration file "
163 "%s for systemd-boots's boot.conf", configfile)
164 else:
165 raise WicError("configfile is specified but failed to "
166 "get it from %s.", configfile)
167
168 if not custom_cfg:
169 # Create systemd-boot configuration using parameters from wks file
170 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
171 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
172 if get_bitbake_var("INITRAMFS_IMAGE"):
173 kernel = "%s-%s.bin" % \
174 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
175
176 title = source_params.get('title')
177
178 boot_conf = ""
179 boot_conf += "title %s\n" % (title if title else "boot")
180 boot_conf += "linux /%s\n" % kernel
181
182 label = source_params.get('label')
183 label_conf = "LABEL=Boot root=%s" % creator.rootdev
184 if label:
185 label_conf = "LABEL=%s" % label
186
187 boot_conf += "options %s %s\n" % \
188 (label_conf, bootloader.append)
189
190 if initrd:
191 initrds = initrd.split(';')
192 for rd in initrds:
193 boot_conf += "initrd /%s\n" % rd
194
195 if dtb:
196 boot_conf += "devicetree /%s\n" % dtb
197
198 if not unified_image:
199 logger.debug("Writing systemd-boot config "
200 "%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
201 cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
202 cfg.write(boot_conf)
203 cfg.close()
204
205
206 @classmethod
207 def do_configure_partition(cls, part, source_params, creator, cr_workdir,
208 oe_builddir, bootimg_dir, kernel_dir,
209 native_sysroot):
210 """
211 Called before do_prepare_partition(), creates loader-specific config
212 """
213 hdddir = "%s/hdd/boot" % cr_workdir
214
215 install_cmd = "install -d %s/EFI/BOOT" % hdddir
216 exec_cmd(install_cmd)
217
218 try:
219 if source_params['loader'] == 'grub-efi':
220 cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params)
221 elif source_params['loader'] == 'systemd-boot':
222 cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
223 elif source_params['loader'] == 'uefi-kernel':
224 pass
225 else:
226 raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
227 except KeyError:
228 raise WicError("bootimg-efi requires a loader, none specified")
229
230 if get_bitbake_var("IMAGE_EFI_BOOT_FILES") is None:
231 logger.debug('No boot files defined in IMAGE_EFI_BOOT_FILES')
232 else:
233 boot_files = None
234 for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)):
235 if fmt:
236 var = fmt % id
237 else:
238 var = ""
239
240 boot_files = get_bitbake_var("IMAGE_EFI_BOOT_FILES" + var)
241 if boot_files:
242 break
243
244 logger.debug('Boot files: %s', boot_files)
245
246 # list of tuples (src_name, dst_name)
247 deploy_files = []
248 for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
249 if ';' in src_entry:
250 dst_entry = tuple(src_entry.split(';'))
251 if not dst_entry[0] or not dst_entry[1]:
252 raise WicError('Malformed boot file entry: %s' % src_entry)
253 else:
254 dst_entry = (src_entry, src_entry)
255
256 logger.debug('Destination entry: %r', dst_entry)
257 deploy_files.append(dst_entry)
258
259 cls.install_task = [];
260 for deploy_entry in deploy_files:
261 src, dst = deploy_entry
262 if '*' in src:
263 # by default install files under their basename
264 entry_name_fn = os.path.basename
265 if dst != src:
266 # unless a target name was given, then treat name
267 # as a directory and append a basename
268 entry_name_fn = lambda name: \
269 os.path.join(dst,
270 os.path.basename(name))
271
272 srcs = glob(os.path.join(kernel_dir, src))
273
274 logger.debug('Globbed sources: %s', ', '.join(srcs))
275 for entry in srcs:
276 src = os.path.relpath(entry, kernel_dir)
277 entry_dst_name = entry_name_fn(entry)
278 cls.install_task.append((src, entry_dst_name))
279 else:
280 cls.install_task.append((src, dst))
281
282 @classmethod
283 def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
284 oe_builddir, bootimg_dir, kernel_dir,
285 rootfs_dir, native_sysroot):
286 """
287 Called to do the actual content population for a partition i.e. it
288 'prepares' the partition to be incorporated into the image.
289 In this case, prepare content for an EFI (grub) boot partition.
290 """
291 if not kernel_dir:
292 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
293 if not kernel_dir:
294 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
295
296 staging_kernel_dir = kernel_dir
297
298 hdddir = "%s/hdd/boot" % cr_workdir
299
300 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
301 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
302 if get_bitbake_var("INITRAMFS_IMAGE"):
303 kernel = "%s-%s.bin" % \
304 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
305
306 if source_params.get('create-unified-kernel-image') == "true":
307 initrd = source_params.get('initrd')
308 if not initrd:
309 raise WicError("initrd= must be specified when create-unified-kernel-image=true, exiting")
310
311 deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
312 efi_stub = glob("%s/%s" % (deploy_dir, "linux*.efi.stub"))
313 if len(efi_stub) == 0:
314 raise WicError("Unified Kernel Image EFI stub not found, exiting")
315 efi_stub = efi_stub[0]
316
317 with tempfile.TemporaryDirectory() as tmp_dir:
318 label = source_params.get('label')
319 label_conf = "root=%s" % creator.rootdev
320 if label:
321 label_conf = "LABEL=%s" % label
322
323 bootloader = creator.ks.bootloader
324 cmdline = open("%s/cmdline" % tmp_dir, "w")
325 cmdline.write("%s %s" % (label_conf, bootloader.append))
326 cmdline.close()
327
328 initrds = initrd.split(';')
329 initrd = open("%s/initrd" % tmp_dir, "wb")
330 for f in initrds:
331 with open("%s/%s" % (deploy_dir, f), 'rb') as in_file:
332 shutil.copyfileobj(in_file, initrd)
333 initrd.close()
334
335 # Searched by systemd-boot:
336 # https://systemd.io/BOOT_LOADER_SPECIFICATION/#type-2-efi-unified-kernel-images
337 install_cmd = "install -d %s/EFI/Linux" % hdddir
338 exec_cmd(install_cmd)
339
340 staging_dir_host = get_bitbake_var("STAGING_DIR_HOST")
341 target_sys = get_bitbake_var("TARGET_SYS")
342
343 objdump_cmd = "%s-objdump" % target_sys
344 objdump_cmd += " -p %s" % efi_stub
345 objdump_cmd += " | awk '{ if ($1 == \"SectionAlignment\"){print $2} }'"
346
347 ret, align_str = exec_native_cmd(objdump_cmd, native_sysroot)
348 align = int(align_str, 16)
349
350 objdump_cmd = "%s-objdump" % target_sys
351 objdump_cmd += " -h %s | tail -2" % efi_stub
352 ret, output = exec_native_cmd(objdump_cmd, native_sysroot)
353
354 offset = int(output.split()[2], 16) + int(output.split()[3], 16)
355
356 osrel_off = offset + align - offset % align
357 osrel_path = "%s/usr/lib/os-release" % staging_dir_host
358 osrel_sz = os.stat(osrel_path).st_size
359
360 cmdline_off = osrel_off + osrel_sz
361 cmdline_off = cmdline_off + align - cmdline_off % align
362 cmdline_sz = os.stat(cmdline.name).st_size
363
364 dtb_off = cmdline_off + cmdline_sz
365 dtb_off = dtb_off + align - dtb_off % align
366
367 dtb = source_params.get('dtb')
368 if dtb:
369 if ';' in dtb:
370 raise WicError("Only one DTB supported, exiting")
371 dtb_path = "%s/%s" % (deploy_dir, dtb)
372 dtb_params = '--add-section .dtb=%s --change-section-vma .dtb=0x%x' % \
373 (dtb_path, dtb_off)
374 linux_off = dtb_off + os.stat(dtb_path).st_size
375 linux_off = linux_off + align - linux_off % align
376 else:
377 dtb_params = ''
378 linux_off = dtb_off
379
380 linux_path = "%s/%s" % (staging_kernel_dir, kernel)
381 linux_sz = os.stat(linux_path).st_size
382
383 initrd_off = linux_off + linux_sz
384 initrd_off = initrd_off + align - initrd_off % align
385
386 # https://www.freedesktop.org/software/systemd/man/systemd-stub.html
387 objcopy_cmd = "%s-objcopy" % target_sys
388 objcopy_cmd += " --enable-deterministic-archives"
389 objcopy_cmd += " --preserve-dates"
390 objcopy_cmd += " --add-section .osrel=%s" % osrel_path
391 objcopy_cmd += " --change-section-vma .osrel=0x%x" % osrel_off
392 objcopy_cmd += " --add-section .cmdline=%s" % cmdline.name
393 objcopy_cmd += " --change-section-vma .cmdline=0x%x" % cmdline_off
394 objcopy_cmd += dtb_params
395 objcopy_cmd += " --add-section .linux=%s" % linux_path
396 objcopy_cmd += " --change-section-vma .linux=0x%x" % linux_off
397 objcopy_cmd += " --add-section .initrd=%s" % initrd.name
398 objcopy_cmd += " --change-section-vma .initrd=0x%x" % initrd_off
399 objcopy_cmd += " %s %s/EFI/Linux/linux.efi" % (efi_stub, hdddir)
400
401 exec_native_cmd(objcopy_cmd, native_sysroot)
402 else:
403 if source_params.get('install-kernel-into-boot-dir') != 'false':
404 install_cmd = "install -m 0644 %s/%s %s/%s" % \
405 (staging_kernel_dir, kernel, hdddir, kernel)
406 exec_cmd(install_cmd)
407
408 if get_bitbake_var("IMAGE_EFI_BOOT_FILES"):
409 for src_path, dst_path in cls.install_task:
410 install_cmd = "install -m 0644 -D %s %s" \
411 % (os.path.join(kernel_dir, src_path),
412 os.path.join(hdddir, dst_path))
413 exec_cmd(install_cmd)
414
415 try:
416 if source_params['loader'] == 'grub-efi':
417 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
418 "%s/grub.cfg" % cr_workdir)
419 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
420 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
421 exec_cmd(cp_cmd, True)
422 shutil.move("%s/grub.cfg" % cr_workdir,
423 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
424 elif source_params['loader'] == 'systemd-boot':
425 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
426 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
427 exec_cmd(cp_cmd, True)
428 elif source_params['loader'] == 'uefi-kernel':
429 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
430 if not kernel:
431 raise WicError("Empty KERNEL_IMAGETYPE %s\n" % target)
432 target = get_bitbake_var("TARGET_SYS")
433 if not target:
434 raise WicError("Unknown arch (TARGET_SYS) %s\n" % target)
435
436 if re.match("x86_64", target):
437 kernel_efi_image = "bootx64.efi"
438 elif re.match('i.86', target):
439 kernel_efi_image = "bootia32.efi"
440 elif re.match('aarch64', target):
441 kernel_efi_image = "bootaa64.efi"
442 elif re.match('arm', target):
443 kernel_efi_image = "bootarm.efi"
444 else:
445 raise WicError("UEFI stub kernel is incompatible with target %s" % target)
446
447 for mod in [x for x in os.listdir(kernel_dir) if x.startswith(kernel)]:
448 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, kernel_efi_image)
449 exec_cmd(cp_cmd, True)
450 else:
451 raise WicError("unrecognized bootimg-efi loader: %s" %
452 source_params['loader'])
453 except KeyError:
454 raise WicError("bootimg-efi requires a loader, none specified")
455
456 startup = os.path.join(kernel_dir, "startup.nsh")
457 if os.path.exists(startup):
458 cp_cmd = "cp %s %s/" % (startup, hdddir)
459 exec_cmd(cp_cmd, True)
460
461 for paths in part.include_path or []:
462 for path in paths:
463 cp_cmd = "cp -r %s %s/" % (path, hdddir)
464 exec_cmd(cp_cmd, True)
465
466 du_cmd = "du -bks %s" % hdddir
467 out = exec_cmd(du_cmd)
468 blocks = int(out.split()[0])
469
470 extra_blocks = part.get_extra_block_count(blocks)
471
472 if extra_blocks < BOOTDD_EXTRA_SPACE:
473 extra_blocks = BOOTDD_EXTRA_SPACE
474
475 blocks += extra_blocks
476
477 logger.debug("Added %d extra blocks to %s to get to %d total blocks",
478 extra_blocks, part.mountpoint, blocks)
479
480 # required for compatibility with certain devices expecting file system
481 # block count to be equal to partition block count
482 if blocks < part.fixed_size:
483 blocks = part.fixed_size
484 logger.debug("Overriding %s to %d total blocks for compatibility",
485 part.mountpoint, blocks)
486
487 # dosfs image, created by mkdosfs
488 bootimg = "%s/boot.img" % cr_workdir
489
490 label = part.label if part.label else "ESP"
491
492 dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \
493 (label, part.fsuuid, bootimg, blocks)
494 exec_native_cmd(dosfs_cmd, native_sysroot)
495
496 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
497 exec_native_cmd(mcopy_cmd, native_sysroot)
498
499 chmod_cmd = "chmod 644 %s" % bootimg
500 exec_cmd(chmod_cmd)
501
502 du_cmd = "du -Lbks %s" % bootimg
503 out = exec_cmd(du_cmd)
504 bootimg_size = out.split()[0]
505
506 part.size = int(bootimg_size)
507 part.source_file = bootimg
diff --git a/scripts/lib/wic/plugins/source/bootimg-partition.py b/scripts/lib/wic/plugins/source/bootimg-partition.py
deleted file mode 100644
index 1071d1af3f..0000000000
--- a/scripts/lib/wic/plugins/source/bootimg-partition.py
+++ /dev/null
@@ -1,197 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'bootimg-partition' source plugin class for
8# 'wic'. The plugin creates an image of boot partition, copying over
9# files listed in IMAGE_BOOT_FILES bitbake variable.
10#
11# AUTHORS
12# Maciej Borzecki <maciej.borzecki (at] open-rnd.pl>
13#
14
15import logging
16import os
17import re
18
19from glob import glob
20
21from wic import WicError
22from wic.engine import get_custom_config
23from wic.pluginbase import SourcePlugin
24from wic.misc import exec_cmd, get_bitbake_var
25
26logger = logging.getLogger('wic')
27
28class BootimgPartitionPlugin(SourcePlugin):
29 """
30 Create an image of boot partition, copying over files
31 listed in IMAGE_BOOT_FILES bitbake variable.
32 """
33
34 name = 'bootimg-partition'
35 image_boot_files_var_name = 'IMAGE_BOOT_FILES'
36
37 @classmethod
38 def do_configure_partition(cls, part, source_params, cr, cr_workdir,
39 oe_builddir, bootimg_dir, kernel_dir,
40 native_sysroot):
41 """
42 Called before do_prepare_partition(), create u-boot specific boot config
43 """
44 hdddir = "%s/boot.%d" % (cr_workdir, part.lineno)
45 install_cmd = "install -d %s" % hdddir
46 exec_cmd(install_cmd)
47
48 if not kernel_dir:
49 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
50 if not kernel_dir:
51 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
52
53 boot_files = None
54 for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)):
55 if fmt:
56 var = fmt % id
57 else:
58 var = ""
59
60 boot_files = get_bitbake_var(cls.image_boot_files_var_name + var)
61 if boot_files is not None:
62 break
63
64 if boot_files is None:
65 raise WicError('No boot files defined, %s unset for entry #%d' % (cls.image_boot_files_var_name, part.lineno))
66
67 logger.debug('Boot files: %s', boot_files)
68
69 # list of tuples (src_name, dst_name)
70 deploy_files = []
71 for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
72 if ';' in src_entry:
73 dst_entry = tuple(src_entry.split(';'))
74 if not dst_entry[0] or not dst_entry[1]:
75 raise WicError('Malformed boot file entry: %s' % src_entry)
76 else:
77 dst_entry = (src_entry, src_entry)
78
79 logger.debug('Destination entry: %r', dst_entry)
80 deploy_files.append(dst_entry)
81
82 cls.install_task = [];
83 for deploy_entry in deploy_files:
84 src, dst = deploy_entry
85 if '*' in src:
86 # by default install files under their basename
87 entry_name_fn = os.path.basename
88 if dst != src:
89 # unless a target name was given, then treat name
90 # as a directory and append a basename
91 entry_name_fn = lambda name: \
92 os.path.join(dst,
93 os.path.basename(name))
94
95 srcs = glob(os.path.join(kernel_dir, src))
96
97 logger.debug('Globbed sources: %s', ', '.join(srcs))
98 for entry in srcs:
99 src = os.path.relpath(entry, kernel_dir)
100 entry_dst_name = entry_name_fn(entry)
101 cls.install_task.append((src, entry_dst_name))
102 else:
103 cls.install_task.append((src, dst))
104
105 if source_params.get('loader') != "u-boot":
106 return
107
108 configfile = cr.ks.bootloader.configfile
109 custom_cfg = None
110 if configfile:
111 custom_cfg = get_custom_config(configfile)
112 if custom_cfg:
113 # Use a custom configuration for extlinux.conf
114 extlinux_conf = custom_cfg
115 logger.debug("Using custom configuration file "
116 "%s for extlinux.conf", configfile)
117 else:
118 raise WicError("configfile is specified but failed to "
119 "get it from %s." % configfile)
120
121 if not custom_cfg:
122 # The kernel types supported by the sysboot of u-boot
123 kernel_types = ["zImage", "Image", "fitImage", "uImage", "vmlinux"]
124 has_dtb = False
125 fdt_dir = '/'
126 kernel_name = None
127
128 # Find the kernel image name, from the highest precedence to lowest
129 for image in kernel_types:
130 for task in cls.install_task:
131 src, dst = task
132 if re.match(image, src):
133 kernel_name = os.path.join('/', dst)
134 break
135 if kernel_name:
136 break
137
138 for task in cls.install_task:
139 src, dst = task
140 # We suppose that all the dtb are in the same directory
141 if re.search(r'\.dtb', src) and fdt_dir == '/':
142 has_dtb = True
143 fdt_dir = os.path.join(fdt_dir, os.path.dirname(dst))
144 break
145
146 if not kernel_name:
147 raise WicError('No kernel file found')
148
149 # Compose the extlinux.conf
150 extlinux_conf = "default Yocto\n"
151 extlinux_conf += "label Yocto\n"
152 extlinux_conf += " kernel %s\n" % kernel_name
153 if has_dtb:
154 extlinux_conf += " fdtdir %s\n" % fdt_dir
155 bootloader = cr.ks.bootloader
156 extlinux_conf += "append root=%s rootwait %s\n" \
157 % (cr.rootdev, bootloader.append if bootloader.append else '')
158
159 install_cmd = "install -d %s/extlinux/" % hdddir
160 exec_cmd(install_cmd)
161 cfg = open("%s/extlinux/extlinux.conf" % hdddir, "w")
162 cfg.write(extlinux_conf)
163 cfg.close()
164
165
166 @classmethod
167 def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
168 oe_builddir, bootimg_dir, kernel_dir,
169 rootfs_dir, native_sysroot):
170 """
171 Called to do the actual content population for a partition i.e. it
172 'prepares' the partition to be incorporated into the image.
173 In this case, does the following:
174 - sets up a vfat partition
175 - copies all files listed in IMAGE_BOOT_FILES variable
176 """
177 hdddir = "%s/boot.%d" % (cr_workdir, part.lineno)
178
179 if not kernel_dir:
180 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
181 if not kernel_dir:
182 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
183
184 logger.debug('Kernel dir: %s', bootimg_dir)
185
186
187 for task in cls.install_task:
188 src_path, dst_path = task
189 logger.debug('Install %s as %s', src_path, dst_path)
190 install_cmd = "install -m 0644 -D %s %s" \
191 % (os.path.join(kernel_dir, src_path),
192 os.path.join(hdddir, dst_path))
193 exec_cmd(install_cmd)
194
195 logger.debug('Prepare boot partition using rootfs in %s', hdddir)
196 part.prepare_rootfs(cr_workdir, oe_builddir, hdddir,
197 native_sysroot, False)
diff --git a/scripts/lib/wic/plugins/source/bootimg-pcbios.py b/scripts/lib/wic/plugins/source/bootimg-pcbios.py
deleted file mode 100644
index a207a83530..0000000000
--- a/scripts/lib/wic/plugins/source/bootimg-pcbios.py
+++ /dev/null
@@ -1,209 +0,0 @@
1#
2# Copyright (c) 2014, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'bootimg-pcbios' source plugin class for 'wic'
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11#
12
13import logging
14import os
15import re
16
17from wic import WicError
18from wic.engine import get_custom_config
19from wic.pluginbase import SourcePlugin
20from wic.misc import (exec_cmd, exec_native_cmd,
21 get_bitbake_var, BOOTDD_EXTRA_SPACE)
22
23logger = logging.getLogger('wic')
24
25class BootimgPcbiosPlugin(SourcePlugin):
26 """
27 Create MBR boot partition and install syslinux on it.
28 """
29
30 name = 'bootimg-pcbios'
31
32 @classmethod
33 def _get_bootimg_dir(cls, bootimg_dir, dirname):
34 """
35 Check if dirname exists in default bootimg_dir or in STAGING_DIR.
36 """
37 staging_datadir = get_bitbake_var("STAGING_DATADIR")
38 for result in (bootimg_dir, staging_datadir):
39 if os.path.exists("%s/%s" % (result, dirname)):
40 return result
41
42 # STAGING_DATADIR is expanded with MLPREFIX if multilib is enabled
43 # but dependency syslinux is still populated to original STAGING_DATADIR
44 nonarch_datadir = re.sub('/[^/]*recipe-sysroot', '/recipe-sysroot', staging_datadir)
45 if os.path.exists(os.path.join(nonarch_datadir, dirname)):
46 return nonarch_datadir
47
48 raise WicError("Couldn't find correct bootimg_dir, exiting")
49
50 @classmethod
51 def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
52 bootimg_dir, kernel_dir, native_sysroot):
53 """
54 Called after all partitions have been prepared and assembled into a
55 disk image. In this case, we install the MBR.
56 """
57 bootimg_dir = cls._get_bootimg_dir(bootimg_dir, 'syslinux')
58 mbrfile = "%s/syslinux/" % bootimg_dir
59 if creator.ptable_format == 'msdos':
60 mbrfile += "mbr.bin"
61 elif creator.ptable_format == 'gpt':
62 mbrfile += "gptmbr.bin"
63 else:
64 raise WicError("Unsupported partition table: %s" %
65 creator.ptable_format)
66
67 if not os.path.exists(mbrfile):
68 raise WicError("Couldn't find %s. If using the -e option, do you "
69 "have the right MACHINE set in local.conf? If not, "
70 "is the bootimg_dir path correct?" % mbrfile)
71
72 full_path = creator._full_path(workdir, disk_name, "direct")
73 logger.debug("Installing MBR on disk %s as %s with size %s bytes",
74 disk_name, full_path, disk.min_size)
75
76 dd_cmd = "dd if=%s of=%s conv=notrunc" % (mbrfile, full_path)
77 exec_cmd(dd_cmd, native_sysroot)
78
79 @classmethod
80 def do_configure_partition(cls, part, source_params, creator, cr_workdir,
81 oe_builddir, bootimg_dir, kernel_dir,
82 native_sysroot):
83 """
84 Called before do_prepare_partition(), creates syslinux config
85 """
86 hdddir = "%s/hdd/boot" % cr_workdir
87
88 install_cmd = "install -d %s" % hdddir
89 exec_cmd(install_cmd)
90
91 bootloader = creator.ks.bootloader
92
93 custom_cfg = None
94 if bootloader.configfile:
95 custom_cfg = get_custom_config(bootloader.configfile)
96 if custom_cfg:
97 # Use a custom configuration for grub
98 syslinux_conf = custom_cfg
99 logger.debug("Using custom configuration file %s "
100 "for syslinux.cfg", bootloader.configfile)
101 else:
102 raise WicError("configfile is specified but failed to "
103 "get it from %s." % bootloader.configfile)
104
105 if not custom_cfg:
106 # Create syslinux configuration using parameters from wks file
107 splash = os.path.join(cr_workdir, "/hdd/boot/splash.jpg")
108 if os.path.exists(splash):
109 splashline = "menu background splash.jpg"
110 else:
111 splashline = ""
112
113 syslinux_conf = ""
114 syslinux_conf += "PROMPT 0\n"
115 syslinux_conf += "TIMEOUT " + str(bootloader.timeout) + "\n"
116 syslinux_conf += "\n"
117 syslinux_conf += "ALLOWOPTIONS 1\n"
118 syslinux_conf += "SERIAL 0 115200\n"
119 syslinux_conf += "\n"
120 if splashline:
121 syslinux_conf += "%s\n" % splashline
122 syslinux_conf += "DEFAULT boot\n"
123 syslinux_conf += "LABEL boot\n"
124
125 kernel = "/" + get_bitbake_var("KERNEL_IMAGETYPE")
126 syslinux_conf += "KERNEL " + kernel + "\n"
127
128 syslinux_conf += "APPEND label=boot root=%s %s\n" % \
129 (creator.rootdev, bootloader.append)
130
131 logger.debug("Writing syslinux config %s/hdd/boot/syslinux.cfg",
132 cr_workdir)
133 cfg = open("%s/hdd/boot/syslinux.cfg" % cr_workdir, "w")
134 cfg.write(syslinux_conf)
135 cfg.close()
136
137 @classmethod
138 def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
139 oe_builddir, bootimg_dir, kernel_dir,
140 rootfs_dir, native_sysroot):
141 """
142 Called to do the actual content population for a partition i.e. it
143 'prepares' the partition to be incorporated into the image.
144 In this case, prepare content for legacy bios boot partition.
145 """
146 bootimg_dir = cls._get_bootimg_dir(bootimg_dir, 'syslinux')
147
148 staging_kernel_dir = kernel_dir
149
150 hdddir = "%s/hdd/boot" % cr_workdir
151
152 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
153 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
154 if get_bitbake_var("INITRAMFS_IMAGE"):
155 kernel = "%s-%s.bin" % \
156 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
157
158 cmds = ("install -m 0644 %s/%s %s/%s" %
159 (staging_kernel_dir, kernel, hdddir, get_bitbake_var("KERNEL_IMAGETYPE")),
160 "install -m 444 %s/syslinux/ldlinux.sys %s/ldlinux.sys" %
161 (bootimg_dir, hdddir),
162 "install -m 0644 %s/syslinux/vesamenu.c32 %s/vesamenu.c32" %
163 (bootimg_dir, hdddir),
164 "install -m 444 %s/syslinux/libcom32.c32 %s/libcom32.c32" %
165 (bootimg_dir, hdddir),
166 "install -m 444 %s/syslinux/libutil.c32 %s/libutil.c32" %
167 (bootimg_dir, hdddir))
168
169 for install_cmd in cmds:
170 exec_cmd(install_cmd)
171
172 du_cmd = "du -bks %s" % hdddir
173 out = exec_cmd(du_cmd)
174 blocks = int(out.split()[0])
175
176 extra_blocks = part.get_extra_block_count(blocks)
177
178 if extra_blocks < BOOTDD_EXTRA_SPACE:
179 extra_blocks = BOOTDD_EXTRA_SPACE
180
181 blocks += extra_blocks
182
183 logger.debug("Added %d extra blocks to %s to get to %d total blocks",
184 extra_blocks, part.mountpoint, blocks)
185
186 # dosfs image, created by mkdosfs
187 bootimg = "%s/boot%s.img" % (cr_workdir, part.lineno)
188
189 label = part.label if part.label else "boot"
190
191 dosfs_cmd = "mkdosfs -n %s -i %s -S 512 -C %s %d" % \
192 (label, part.fsuuid, bootimg, blocks)
193 exec_native_cmd(dosfs_cmd, native_sysroot)
194
195 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
196 exec_native_cmd(mcopy_cmd, native_sysroot)
197
198 syslinux_cmd = "syslinux %s" % bootimg
199 exec_native_cmd(syslinux_cmd, native_sysroot)
200
201 chmod_cmd = "chmod 644 %s" % bootimg
202 exec_cmd(chmod_cmd)
203
204 du_cmd = "du -Lbks %s" % bootimg
205 out = exec_cmd(du_cmd)
206 bootimg_size = out.split()[0]
207
208 part.size = int(bootimg_size)
209 part.source_file = bootimg
diff --git a/scripts/lib/wic/plugins/source/empty.py b/scripts/lib/wic/plugins/source/empty.py
deleted file mode 100644
index 4178912377..0000000000
--- a/scripts/lib/wic/plugins/source/empty.py
+++ /dev/null
@@ -1,89 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7# The empty wic plugin is used to create unformatted empty partitions for wic
8# images.
9# To use it you must pass "empty" as argument for the "--source" parameter in
10# the wks file. For example:
11# part foo --source empty --ondisk sda --size="1024" --align 1024
12#
13# The plugin supports writing zeros to the start of the
14# partition. This is useful to overwrite old content like
15# filesystem signatures which may be re-recognized otherwise.
16# This feature can be enabled with
17# '--sourceparams="[fill|size=<N>[S|s|K|k|M|G]][,][bs=<N>[S|s|K|k|M|G]]"'
18# Conflicting or missing options throw errors.
19
20import logging
21import os
22
23from wic import WicError
24from wic.ksparser import sizetype
25from wic.pluginbase import SourcePlugin
26
27logger = logging.getLogger('wic')
28
29class EmptyPartitionPlugin(SourcePlugin):
30 """
31 Populate unformatted empty partition.
32
33 The following sourceparams are supported:
34 - fill
35 Fill the entire partition with zeros. Requires '--fixed-size' option
36 to be set.
37 - size=<N>[S|s|K|k|M|G]
38 Set the first N bytes of the partition to zero. Default unit is 'K'.
39 - bs=<N>[S|s|K|k|M|G]
40 Write at most N bytes at a time during source file creation.
41 Defaults to '1M'. Default unit is 'K'.
42 """
43
44 name = 'empty'
45
46 @classmethod
47 def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
48 oe_builddir, bootimg_dir, kernel_dir,
49 rootfs_dir, native_sysroot):
50 """
51 Called to do the actual content population for a partition i.e. it
52 'prepares' the partition to be incorporated into the image.
53 """
54 get_byte_count = sizetype('K', True)
55 size = 0
56
57 if 'fill' in source_params and 'size' in source_params:
58 raise WicError("Conflicting source parameters 'fill' and 'size' specified, exiting.")
59
60 # Set the size of the zeros to be written to the partition
61 if 'fill' in source_params:
62 if part.fixed_size == 0:
63 raise WicError("Source parameter 'fill' only works with the '--fixed-size' option, exiting.")
64 size = get_byte_count(part.fixed_size)
65 elif 'size' in source_params:
66 size = get_byte_count(source_params['size'])
67
68 if size == 0:
69 # Nothing to do, create empty partition
70 return
71
72 if 'bs' in source_params:
73 bs = get_byte_count(source_params['bs'])
74 else:
75 bs = get_byte_count('1M')
76
77 # Create a binary file of the requested size filled with zeros
78 source_file = os.path.join(cr_workdir, 'empty-plugin-zeros%s.bin' % part.lineno)
79 if not os.path.exists(os.path.dirname(source_file)):
80 os.makedirs(os.path.dirname(source_file))
81
82 quotient, remainder = divmod(size, bs)
83 with open(source_file, 'wb') as file:
84 for _ in range(quotient):
85 file.write(bytearray(bs))
86 file.write(bytearray(remainder))
87
88 part.size = (size + 1024 - 1) // 1024 # size in KB rounded up
89 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
deleted file mode 100644
index 607356ad13..0000000000
--- a/scripts/lib/wic/plugins/source/isoimage-isohybrid.py
+++ /dev/null
@@ -1,463 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'isoimage-isohybrid' source plugin class for 'wic'
8#
9# AUTHORS
10# Mihaly Varga <mihaly.varga (at] ni.com>
11
12import glob
13import logging
14import os
15import re
16import shutil
17
18from wic import WicError
19from wic.engine import get_custom_config
20from wic.pluginbase import SourcePlugin
21from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
22
23logger = logging.getLogger('wic')
24
25class IsoImagePlugin(SourcePlugin):
26 """
27 Create a bootable ISO image
28
29 This plugin creates a hybrid, legacy and EFI bootable ISO image. The
30 generated image can be used on optical media as well as USB media.
31
32 Legacy boot uses syslinux and EFI boot uses grub or gummiboot (not
33 implemented yet) as bootloader. The plugin creates the directories required
34 by bootloaders and populates them by creating and configuring the
35 bootloader files.
36
37 Example kickstart file:
38 part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi, \\
39 image_name= IsoImage" --ondisk cd --label LIVECD
40 bootloader --timeout=10 --append=" "
41
42 In --sourceparams "loader" specifies the bootloader used for booting in EFI
43 mode, while "image_name" specifies the name of the generated image. In the
44 example above, wic creates an ISO image named IsoImage-cd.direct (default
45 extension added by direct imeger plugin) and a file named IsoImage-cd.iso
46 """
47
48 name = 'isoimage-isohybrid'
49
50 @classmethod
51 def do_configure_syslinux(cls, creator, cr_workdir):
52 """
53 Create loader-specific (syslinux) config
54 """
55 splash = os.path.join(cr_workdir, "ISO/boot/splash.jpg")
56 if os.path.exists(splash):
57 splashline = "menu background splash.jpg"
58 else:
59 splashline = ""
60
61 bootloader = creator.ks.bootloader
62
63 syslinux_conf = ""
64 syslinux_conf += "PROMPT 0\n"
65 syslinux_conf += "TIMEOUT %s \n" % (bootloader.timeout or 10)
66 syslinux_conf += "\n"
67 syslinux_conf += "ALLOWOPTIONS 1\n"
68 syslinux_conf += "SERIAL 0 115200\n"
69 syslinux_conf += "\n"
70 if splashline:
71 syslinux_conf += "%s\n" % splashline
72 syslinux_conf += "DEFAULT boot\n"
73 syslinux_conf += "LABEL boot\n"
74
75 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
76 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
77 if get_bitbake_var("INITRAMFS_IMAGE"):
78 kernel = "%s-%s.bin" % \
79 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
80
81 syslinux_conf += "KERNEL /" + kernel + "\n"
82 syslinux_conf += "APPEND initrd=/initrd LABEL=boot %s\n" \
83 % bootloader.append
84
85 logger.debug("Writing syslinux config %s/ISO/isolinux/isolinux.cfg",
86 cr_workdir)
87
88 with open("%s/ISO/isolinux/isolinux.cfg" % cr_workdir, "w") as cfg:
89 cfg.write(syslinux_conf)
90
91 @classmethod
92 def do_configure_grubefi(cls, part, creator, target_dir):
93 """
94 Create loader-specific (grub-efi) config
95 """
96 configfile = creator.ks.bootloader.configfile
97 if configfile:
98 grubefi_conf = get_custom_config(configfile)
99 if grubefi_conf:
100 logger.debug("Using custom configuration file %s for grub.cfg",
101 configfile)
102 else:
103 raise WicError("configfile is specified "
104 "but failed to get it from %s", configfile)
105 else:
106 splash = os.path.join(target_dir, "splash.jpg")
107 if os.path.exists(splash):
108 splashline = "menu background splash.jpg"
109 else:
110 splashline = ""
111
112 bootloader = creator.ks.bootloader
113
114 grubefi_conf = ""
115 grubefi_conf += "serial --unit=0 --speed=115200 --word=8 "
116 grubefi_conf += "--parity=no --stop=1\n"
117 grubefi_conf += "default=boot\n"
118 grubefi_conf += "timeout=%s\n" % (bootloader.timeout or 10)
119 grubefi_conf += "\n"
120 grubefi_conf += "search --set=root --label %s " % part.label
121 grubefi_conf += "\n"
122 grubefi_conf += "menuentry 'boot'{\n"
123
124 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
125 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
126 if get_bitbake_var("INITRAMFS_IMAGE"):
127 kernel = "%s-%s.bin" % \
128 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
129
130 grubefi_conf += "linux /%s rootwait %s\n" \
131 % (kernel, bootloader.append)
132 grubefi_conf += "initrd /initrd \n"
133 grubefi_conf += "}\n"
134
135 if splashline:
136 grubefi_conf += "%s\n" % splashline
137
138 cfg_path = os.path.join(target_dir, "grub.cfg")
139 logger.debug("Writing grubefi config %s", cfg_path)
140
141 with open(cfg_path, "w") as cfg:
142 cfg.write(grubefi_conf)
143
144 @staticmethod
145 def _build_initramfs_path(rootfs_dir, cr_workdir):
146 """
147 Create path for initramfs image
148 """
149
150 initrd = get_bitbake_var("INITRD_LIVE") or get_bitbake_var("INITRD")
151 if not initrd:
152 initrd_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
153 if not initrd_dir:
154 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting.")
155
156 image_name = get_bitbake_var("IMAGE_BASENAME")
157 if not image_name:
158 raise WicError("Couldn't find IMAGE_BASENAME, exiting.")
159
160 image_type = get_bitbake_var("INITRAMFS_FSTYPES")
161 if not image_type:
162 raise WicError("Couldn't find INITRAMFS_FSTYPES, exiting.")
163
164 machine = os.path.basename(initrd_dir)
165
166 pattern = '%s/%s*%s.%s' % (initrd_dir, image_name, machine, image_type)
167 files = glob.glob(pattern)
168 if files:
169 initrd = files[0]
170
171 if not initrd or not os.path.exists(initrd):
172 # Create initrd from rootfs directory
173 initrd = "%s/initrd.cpio.gz" % cr_workdir
174 initrd_dir = "%s/INITRD" % cr_workdir
175 shutil.copytree("%s" % rootfs_dir, \
176 "%s" % initrd_dir, symlinks=True)
177
178 if os.path.isfile("%s/init" % rootfs_dir):
179 shutil.copy2("%s/init" % rootfs_dir, "%s/init" % initrd_dir)
180 elif os.path.lexists("%s/init" % rootfs_dir):
181 os.symlink(os.readlink("%s/init" % rootfs_dir), \
182 "%s/init" % initrd_dir)
183 elif os.path.isfile("%s/sbin/init" % rootfs_dir):
184 shutil.copy2("%s/sbin/init" % rootfs_dir, \
185 "%s" % initrd_dir)
186 elif os.path.lexists("%s/sbin/init" % rootfs_dir):
187 os.symlink(os.readlink("%s/sbin/init" % rootfs_dir), \
188 "%s/init" % initrd_dir)
189 else:
190 raise WicError("Couldn't find or build initrd, exiting.")
191
192 exec_cmd("cd %s && find . | cpio -o -H newc -R root:root >%s/initrd.cpio " \
193 % (initrd_dir, cr_workdir), as_shell=True)
194 exec_cmd("gzip -f -9 %s/initrd.cpio" % cr_workdir, as_shell=True)
195 shutil.rmtree(initrd_dir)
196
197 return initrd
198
199 @classmethod
200 def do_configure_partition(cls, part, source_params, creator, cr_workdir,
201 oe_builddir, bootimg_dir, kernel_dir,
202 native_sysroot):
203 """
204 Called before do_prepare_partition(), creates loader-specific config
205 """
206 isodir = "%s/ISO/" % cr_workdir
207
208 if os.path.exists(isodir):
209 shutil.rmtree(isodir)
210
211 install_cmd = "install -d %s " % isodir
212 exec_cmd(install_cmd)
213
214 # Overwrite the name of the created image
215 logger.debug(source_params)
216 if 'image_name' in source_params and \
217 source_params['image_name'].strip():
218 creator.name = source_params['image_name'].strip()
219 logger.debug("The name of the image is: %s", creator.name)
220
221 @staticmethod
222 def _install_payload(source_params, iso_dir):
223 """
224 Copies contents of payload directory (as specified in 'payload_dir' param) into iso_dir
225 """
226
227 if source_params.get('payload_dir'):
228 payload_dir = source_params['payload_dir']
229
230 logger.debug("Payload directory: %s", payload_dir)
231 shutil.copytree(payload_dir, iso_dir, symlinks=True, dirs_exist_ok=True)
232
233 @classmethod
234 def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
235 oe_builddir, bootimg_dir, kernel_dir,
236 rootfs_dir, native_sysroot):
237 """
238 Called to do the actual content population for a partition i.e. it
239 'prepares' the partition to be incorporated into the image.
240 In this case, prepare content for a bootable ISO image.
241 """
242
243 isodir = "%s/ISO" % cr_workdir
244
245 cls._install_payload(source_params, isodir)
246
247 if part.rootfs_dir is None:
248 if not 'ROOTFS_DIR' in rootfs_dir:
249 raise WicError("Couldn't find --rootfs-dir, exiting.")
250 rootfs_dir = rootfs_dir['ROOTFS_DIR']
251 else:
252 if part.rootfs_dir in rootfs_dir:
253 rootfs_dir = rootfs_dir[part.rootfs_dir]
254 elif part.rootfs_dir:
255 rootfs_dir = part.rootfs_dir
256 else:
257 raise WicError("Couldn't find --rootfs-dir=%s connection "
258 "or it is not a valid path, exiting." %
259 part.rootfs_dir)
260
261 if not os.path.isdir(rootfs_dir):
262 rootfs_dir = get_bitbake_var("IMAGE_ROOTFS")
263 if not os.path.isdir(rootfs_dir):
264 raise WicError("Couldn't find IMAGE_ROOTFS, exiting.")
265
266 part.rootfs_dir = rootfs_dir
267 deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
268 img_iso_dir = get_bitbake_var("ISODIR")
269
270 # Remove the temporary file created by part.prepare_rootfs()
271 if os.path.isfile(part.source_file):
272 os.remove(part.source_file)
273
274 # Support using a different initrd other than default
275 if source_params.get('initrd'):
276 initrd = source_params['initrd']
277 if not deploy_dir:
278 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
279 cp_cmd = "cp %s/%s %s" % (deploy_dir, initrd, cr_workdir)
280 exec_cmd(cp_cmd)
281 else:
282 # Prepare initial ramdisk
283 initrd = "%s/initrd" % deploy_dir
284 if not os.path.isfile(initrd):
285 initrd = "%s/initrd" % img_iso_dir
286 if not os.path.isfile(initrd):
287 initrd = cls._build_initramfs_path(rootfs_dir, cr_workdir)
288
289 install_cmd = "install -m 0644 %s %s/initrd" % (initrd, isodir)
290 exec_cmd(install_cmd)
291
292 # Remove the temporary file created by _build_initramfs_path function
293 if os.path.isfile("%s/initrd.cpio.gz" % cr_workdir):
294 os.remove("%s/initrd.cpio.gz" % cr_workdir)
295
296 kernel = get_bitbake_var("KERNEL_IMAGETYPE")
297 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
298 if get_bitbake_var("INITRAMFS_IMAGE"):
299 kernel = "%s-%s.bin" % \
300 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
301
302 install_cmd = "install -m 0644 %s/%s %s/%s" % \
303 (kernel_dir, kernel, isodir, kernel)
304 exec_cmd(install_cmd)
305
306 #Create bootloader for efi boot
307 try:
308 target_dir = "%s/EFI/BOOT" % isodir
309 if os.path.exists(target_dir):
310 shutil.rmtree(target_dir)
311
312 os.makedirs(target_dir)
313
314 if source_params['loader'] == 'grub-efi':
315 # Builds bootx64.efi/bootia32.efi if ISODIR didn't exist or
316 # didn't contains it
317 target_arch = get_bitbake_var("TARGET_SYS")
318 if not target_arch:
319 raise WicError("Coludn't find target architecture")
320
321 if re.match("x86_64", target_arch):
322 grub_src_image = "grub-efi-bootx64.efi"
323 grub_dest_image = "bootx64.efi"
324 elif re.match('i.86', target_arch):
325 grub_src_image = "grub-efi-bootia32.efi"
326 grub_dest_image = "bootia32.efi"
327 else:
328 raise WicError("grub-efi is incompatible with target %s" %
329 target_arch)
330
331 grub_target = os.path.join(target_dir, grub_dest_image)
332 if not os.path.isfile(grub_target):
333 grub_src = os.path.join(deploy_dir, grub_src_image)
334 if not os.path.exists(grub_src):
335 raise WicError("Grub loader %s is not found in %s. "
336 "Please build grub-efi first" % (grub_src_image, deploy_dir))
337 shutil.copy(grub_src, grub_target)
338
339 if not os.path.isfile(os.path.join(target_dir, "boot.cfg")):
340 cls.do_configure_grubefi(part, creator, target_dir)
341
342 else:
343 raise WicError("unrecognized bootimg-efi loader: %s" %
344 source_params['loader'])
345 except KeyError:
346 raise WicError("bootimg-efi requires a loader, none specified")
347
348 # Create efi.img that contains bootloader files for EFI booting
349 # if ISODIR didn't exist or didn't contains it
350 if os.path.isfile("%s/efi.img" % img_iso_dir):
351 install_cmd = "install -m 0644 %s/efi.img %s/efi.img" % \
352 (img_iso_dir, isodir)
353 exec_cmd(install_cmd)
354 else:
355 # Default to 100 blocks of extra space for file system overhead
356 esp_extra_blocks = int(source_params.get('esp_extra_blocks', '100'))
357
358 du_cmd = "du -bks %s/EFI" % isodir
359 out = exec_cmd(du_cmd)
360 blocks = int(out.split()[0])
361 blocks += esp_extra_blocks
362 logger.debug("Added 100 extra blocks to %s to get to %d "
363 "total blocks", part.mountpoint, blocks)
364
365 # dosfs image for EFI boot
366 bootimg = "%s/efi.img" % isodir
367
368 esp_label = source_params.get('esp_label', 'EFIimg')
369
370 dosfs_cmd = 'mkfs.vfat -n \'%s\' -S 512 -C %s %d' \
371 % (esp_label, bootimg, blocks)
372 exec_native_cmd(dosfs_cmd, native_sysroot)
373
374 mmd_cmd = "mmd -i %s ::/EFI" % bootimg
375 exec_native_cmd(mmd_cmd, native_sysroot)
376
377 mcopy_cmd = "mcopy -i %s -s %s/EFI/* ::/EFI/" \
378 % (bootimg, isodir)
379 exec_native_cmd(mcopy_cmd, native_sysroot)
380
381 chmod_cmd = "chmod 644 %s" % bootimg
382 exec_cmd(chmod_cmd)
383
384 # Prepare files for legacy boot
385 syslinux_dir = get_bitbake_var("STAGING_DATADIR")
386 if not syslinux_dir:
387 raise WicError("Couldn't find STAGING_DATADIR, exiting.")
388
389 if os.path.exists("%s/isolinux" % isodir):
390 shutil.rmtree("%s/isolinux" % isodir)
391
392 install_cmd = "install -d %s/isolinux" % isodir
393 exec_cmd(install_cmd)
394
395 cls.do_configure_syslinux(creator, cr_workdir)
396
397 install_cmd = "install -m 444 %s/syslinux/ldlinux.sys " % syslinux_dir
398 install_cmd += "%s/isolinux/ldlinux.sys" % isodir
399 exec_cmd(install_cmd)
400
401 install_cmd = "install -m 444 %s/syslinux/isohdpfx.bin " % syslinux_dir
402 install_cmd += "%s/isolinux/isohdpfx.bin" % isodir
403 exec_cmd(install_cmd)
404
405 install_cmd = "install -m 644 %s/syslinux/isolinux.bin " % syslinux_dir
406 install_cmd += "%s/isolinux/isolinux.bin" % isodir
407 exec_cmd(install_cmd)
408
409 install_cmd = "install -m 644 %s/syslinux/ldlinux.c32 " % syslinux_dir
410 install_cmd += "%s/isolinux/ldlinux.c32" % isodir
411 exec_cmd(install_cmd)
412
413 #create ISO image
414 iso_img = "%s/tempiso_img.iso" % cr_workdir
415 iso_bootimg = "isolinux/isolinux.bin"
416 iso_bootcat = "isolinux/boot.cat"
417 efi_img = "efi.img"
418
419 mkisofs_cmd = "mkisofs -V %s " % part.label
420 mkisofs_cmd += "-o %s -U " % iso_img
421 mkisofs_cmd += "-J -joliet-long -r -iso-level 2 -b %s " % iso_bootimg
422 mkisofs_cmd += "-c %s -no-emul-boot -boot-load-size 4 " % iso_bootcat
423 mkisofs_cmd += "-boot-info-table -eltorito-alt-boot "
424 mkisofs_cmd += "-eltorito-platform 0xEF -eltorito-boot %s " % efi_img
425 mkisofs_cmd += "-no-emul-boot %s " % isodir
426
427 logger.debug("running command: %s", mkisofs_cmd)
428 exec_native_cmd(mkisofs_cmd, native_sysroot)
429
430 shutil.rmtree(isodir)
431
432 du_cmd = "du -Lbks %s" % iso_img
433 out = exec_cmd(du_cmd)
434 isoimg_size = int(out.split()[0])
435
436 part.size = isoimg_size
437 part.source_file = iso_img
438
439 @classmethod
440 def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
441 bootimg_dir, kernel_dir, native_sysroot):
442 """
443 Called after all partitions have been prepared and assembled into a
444 disk image. In this case, we insert/modify the MBR using isohybrid
445 utility for booting via BIOS from disk storage devices.
446 """
447
448 iso_img = "%s.p1" % disk.path
449 full_path = creator._full_path(workdir, disk_name, "direct")
450 full_path_iso = creator._full_path(workdir, disk_name, "iso")
451
452 isohybrid_cmd = "isohybrid -u %s" % iso_img
453 logger.debug("running command: %s", isohybrid_cmd)
454 exec_native_cmd(isohybrid_cmd, native_sysroot)
455
456 # Replace the image created by direct plugin with the one created by
457 # mkisofs command. This is necessary because the iso image created by
458 # mkisofs has a very specific MBR is system area of the ISO image, and
459 # direct plugin adds and configures an another MBR.
460 logger.debug("Replaceing the image created by direct plugin\n")
461 os.remove(disk.path)
462 shutil.copy2(iso_img, full_path_iso)
463 shutil.copy2(full_path_iso, full_path)
diff --git a/scripts/lib/wic/plugins/source/rawcopy.py b/scripts/lib/wic/plugins/source/rawcopy.py
deleted file mode 100644
index 21903c2f23..0000000000
--- a/scripts/lib/wic/plugins/source/rawcopy.py
+++ /dev/null
@@ -1,115 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import logging
8import os
9import signal
10import subprocess
11
12from wic import WicError
13from wic.pluginbase import SourcePlugin
14from wic.misc import exec_cmd, get_bitbake_var
15from wic.filemap import sparse_copy
16
17logger = logging.getLogger('wic')
18
19class RawCopyPlugin(SourcePlugin):
20 """
21 Populate partition content from raw image file.
22 """
23
24 name = 'rawcopy'
25
26 @staticmethod
27 def do_image_label(fstype, dst, label):
28 # don't create label when fstype is none
29 if fstype == 'none':
30 return
31
32 if fstype.startswith('ext'):
33 cmd = 'tune2fs -L %s %s' % (label, dst)
34 elif fstype in ('msdos', 'vfat'):
35 cmd = 'dosfslabel %s %s' % (dst, label)
36 elif fstype == 'btrfs':
37 cmd = 'btrfs filesystem label %s %s' % (dst, label)
38 elif fstype == 'swap':
39 cmd = 'mkswap -L %s %s' % (label, dst)
40 elif fstype in ('squashfs', 'erofs'):
41 raise WicError("It's not possible to update a %s "
42 "filesystem label '%s'" % (fstype, label))
43 else:
44 raise WicError("Cannot update filesystem label: "
45 "Unknown fstype: '%s'" % (fstype))
46
47 exec_cmd(cmd)
48
49 @staticmethod
50 def do_image_uncompression(src, dst, workdir):
51 def subprocess_setup():
52 # Python installs a SIGPIPE handler by default. This is usually not what
53 # non-Python subprocesses expect.
54 # SIGPIPE errors are known issues with gzip/bash
55 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
56
57 extension = os.path.splitext(src)[1]
58 decompressor = {
59 ".bz2": "bzip2",
60 ".gz": "gzip",
61 ".xz": "xz",
62 ".zst": "zstd -f",
63 }.get(extension)
64 if not decompressor:
65 raise WicError("Not supported compressor filename extension: %s" % extension)
66 cmd = "%s -dc %s > %s" % (decompressor, src, dst)
67 subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=workdir)
68
69 @classmethod
70 def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
71 oe_builddir, bootimg_dir, kernel_dir,
72 rootfs_dir, native_sysroot):
73 """
74 Called to do the actual content population for a partition i.e. it
75 'prepares' the partition to be incorporated into the image.
76 """
77 if not kernel_dir:
78 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
79 if not kernel_dir:
80 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
81
82 logger.debug('Kernel dir: %s', kernel_dir)
83
84 if 'file' not in source_params:
85 raise WicError("No file specified")
86
87 if 'unpack' in source_params:
88 img = os.path.join(kernel_dir, source_params['file'])
89 src = os.path.join(cr_workdir, os.path.splitext(source_params['file'])[0])
90 RawCopyPlugin.do_image_uncompression(img, src, cr_workdir)
91 else:
92 src = os.path.join(kernel_dir, source_params['file'])
93
94 dst = os.path.join(cr_workdir, "%s.%s" % (os.path.basename(source_params['file']), part.lineno))
95
96 if not os.path.exists(os.path.dirname(dst)):
97 os.makedirs(os.path.dirname(dst))
98
99 if 'skip' in source_params:
100 sparse_copy(src, dst, skip=int(source_params['skip']))
101 else:
102 sparse_copy(src, dst)
103
104 # get the size in the right units for kickstart (kB)
105 du_cmd = "du -Lbks %s" % dst
106 out = exec_cmd(du_cmd)
107 filesize = int(out.split()[0])
108
109 if filesize > part.size:
110 part.size = filesize
111
112 if part.label:
113 RawCopyPlugin.do_image_label(part.fstype, dst, part.label)
114
115 part.source_file = dst
diff --git a/scripts/lib/wic/plugins/source/rootfs.py b/scripts/lib/wic/plugins/source/rootfs.py
deleted file mode 100644
index c990143c0d..0000000000
--- a/scripts/lib/wic/plugins/source/rootfs.py
+++ /dev/null
@@ -1,236 +0,0 @@
1#
2# Copyright (c) 2014, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'rootfs' source plugin class for 'wic'
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11# Joao Henrique Ferreira de Freitas <joaohf (at] gmail.com>
12#
13
14import logging
15import os
16import shutil
17import sys
18
19from oe.path import copyhardlinktree
20from pathlib import Path
21
22from wic import WicError
23from wic.pluginbase import SourcePlugin
24from wic.misc import get_bitbake_var, exec_native_cmd
25
26logger = logging.getLogger('wic')
27
28class RootfsPlugin(SourcePlugin):
29 """
30 Populate partition content from a rootfs directory.
31 """
32
33 name = 'rootfs'
34
35 @staticmethod
36 def __validate_path(cmd, rootfs_dir, path):
37 if os.path.isabs(path):
38 logger.error("%s: Must be relative: %s" % (cmd, path))
39 sys.exit(1)
40
41 # Disallow climbing outside of parent directory using '..',
42 # because doing so could be quite disastrous (we will delete the
43 # directory, or modify a directory outside OpenEmbedded).
44 full_path = os.path.realpath(os.path.join(rootfs_dir, path))
45 if not full_path.startswith(os.path.realpath(rootfs_dir)):
46 logger.error("%s: Must point inside the rootfs: %s" % (cmd, path))
47 sys.exit(1)
48
49 return full_path
50
51 @staticmethod
52 def __get_rootfs_dir(rootfs_dir):
53 if rootfs_dir and os.path.isdir(rootfs_dir):
54 return os.path.realpath(rootfs_dir)
55
56 image_rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", rootfs_dir)
57 if not os.path.isdir(image_rootfs_dir):
58 raise WicError("No valid artifact IMAGE_ROOTFS from image "
59 "named %s has been found at %s, exiting." %
60 (rootfs_dir, image_rootfs_dir))
61
62 return os.path.realpath(image_rootfs_dir)
63
64 @staticmethod
65 def __get_pseudo(native_sysroot, rootfs, pseudo_dir):
66 pseudo = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
67 pseudo += "export PSEUDO_LOCALSTATEDIR=%s;" % pseudo_dir
68 pseudo += "export PSEUDO_PASSWD=%s;" % rootfs
69 pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
70 pseudo += "%s " % get_bitbake_var("FAKEROOTCMD")
71 return pseudo
72
73 @classmethod
74 def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
75 oe_builddir, bootimg_dir, kernel_dir,
76 krootfs_dir, native_sysroot):
77 """
78 Called to do the actual content population for a partition i.e. it
79 'prepares' the partition to be incorporated into the image.
80 In this case, prepare content for legacy bios boot partition.
81 """
82 if part.rootfs_dir is None:
83 if not 'ROOTFS_DIR' in krootfs_dir:
84 raise WicError("Couldn't find --rootfs-dir, exiting")
85
86 rootfs_dir = krootfs_dir['ROOTFS_DIR']
87 else:
88 if part.rootfs_dir in krootfs_dir:
89 rootfs_dir = krootfs_dir[part.rootfs_dir]
90 elif part.rootfs_dir:
91 rootfs_dir = part.rootfs_dir
92 else:
93 raise WicError("Couldn't find --rootfs-dir=%s connection or "
94 "it is not a valid path, exiting" % part.rootfs_dir)
95
96 part.rootfs_dir = cls.__get_rootfs_dir(rootfs_dir)
97 part.has_fstab = os.path.exists(os.path.join(part.rootfs_dir, "etc/fstab"))
98 pseudo_dir = os.path.join(part.rootfs_dir, "../pseudo")
99 if not os.path.lexists(pseudo_dir):
100 pseudo_dir = os.path.join(cls.__get_rootfs_dir(None), '../pseudo')
101
102 if not os.path.lexists(pseudo_dir):
103 logger.warn("%s folder does not exist. "
104 "Usernames and permissions will be invalid " % pseudo_dir)
105 pseudo_dir = None
106
107 new_rootfs = None
108 new_pseudo = None
109 # Handle excluded paths.
110 if part.exclude_path or part.include_path or part.change_directory or part.update_fstab_in_rootfs:
111 # We need a new rootfs directory we can safely modify without
112 # interfering with other tasks. Copy to workdir.
113 new_rootfs = os.path.realpath(os.path.join(cr_workdir, "rootfs%d" % part.lineno))
114
115 if os.path.lexists(new_rootfs):
116 shutil.rmtree(os.path.join(new_rootfs))
117
118 if part.change_directory:
119 cd = part.change_directory
120 if cd[-1] == '/':
121 cd = cd[:-1]
122 orig_dir = cls.__validate_path("--change-directory", part.rootfs_dir, cd)
123 else:
124 orig_dir = part.rootfs_dir
125 copyhardlinktree(orig_dir, new_rootfs)
126
127 # Convert the pseudo directory to its new location
128 if (pseudo_dir):
129 new_pseudo = os.path.realpath(
130 os.path.join(cr_workdir, "pseudo%d" % part.lineno))
131 if os.path.lexists(new_pseudo):
132 shutil.rmtree(new_pseudo)
133 os.mkdir(new_pseudo)
134 shutil.copy(os.path.join(pseudo_dir, "files.db"),
135 os.path.join(new_pseudo, "files.db"))
136
137 pseudo_cmd = "%s -B -m %s -M %s" % (cls.__get_pseudo(native_sysroot,
138 new_rootfs,
139 new_pseudo),
140 orig_dir, new_rootfs)
141 exec_native_cmd(pseudo_cmd, native_sysroot)
142
143 for in_path in part.include_path or []:
144 #parse arguments
145 include_path = in_path[0]
146 if len(in_path) > 2:
147 logger.error("'Invalid number of arguments for include-path")
148 sys.exit(1)
149 if len(in_path) == 2:
150 path = in_path[1]
151 else:
152 path = None
153
154 # Pack files to be included into a tar file.
155 # We need to create a tar file, because that way we can keep the
156 # permissions from the files even when they belong to different
157 # pseudo enviroments.
158 # If we simply copy files using copyhardlinktree/copytree... the
159 # copied files will belong to the user running wic.
160 tar_file = os.path.realpath(
161 os.path.join(cr_workdir, "include-path%d.tar" % part.lineno))
162 if os.path.isfile(include_path):
163 parent = os.path.dirname(os.path.realpath(include_path))
164 tar_cmd = "tar c --owner=root --group=root -f %s -C %s %s" % (
165 tar_file, parent, os.path.relpath(include_path, parent))
166 exec_native_cmd(tar_cmd, native_sysroot)
167 else:
168 if include_path in krootfs_dir:
169 include_path = krootfs_dir[include_path]
170 include_path = cls.__get_rootfs_dir(include_path)
171 include_pseudo = os.path.join(include_path, "../pseudo")
172 if os.path.lexists(include_pseudo):
173 pseudo = cls.__get_pseudo(native_sysroot, include_path,
174 include_pseudo)
175 tar_cmd = "tar cf %s -C %s ." % (tar_file, include_path)
176 else:
177 pseudo = None
178 tar_cmd = "tar c --owner=root --group=root -f %s -C %s ." % (
179 tar_file, include_path)
180 exec_native_cmd(tar_cmd, native_sysroot, pseudo)
181
182 #create destination
183 if path:
184 destination = cls.__validate_path("--include-path", new_rootfs, path)
185 Path(destination).mkdir(parents=True, exist_ok=True)
186 else:
187 destination = new_rootfs
188
189 #extract destination
190 untar_cmd = "tar xf %s -C %s" % (tar_file, destination)
191 if new_pseudo:
192 pseudo = cls.__get_pseudo(native_sysroot, new_rootfs, new_pseudo)
193 else:
194 pseudo = None
195 exec_native_cmd(untar_cmd, native_sysroot, pseudo)
196 os.remove(tar_file)
197
198 for orig_path in part.exclude_path or []:
199 path = orig_path
200
201 full_path = cls.__validate_path("--exclude-path", new_rootfs, path)
202
203 if not os.path.lexists(full_path):
204 continue
205
206 if new_pseudo:
207 pseudo = cls.__get_pseudo(native_sysroot, new_rootfs, new_pseudo)
208 else:
209 pseudo = None
210 if path.endswith(os.sep):
211 # Delete content only.
212 for entry in os.listdir(full_path):
213 full_entry = os.path.join(full_path, entry)
214 rm_cmd = "rm -rf %s" % (full_entry)
215 exec_native_cmd(rm_cmd, native_sysroot, pseudo)
216 else:
217 # Delete whole directory.
218 rm_cmd = "rm -rf %s" % (full_path)
219 exec_native_cmd(rm_cmd, native_sysroot, pseudo)
220
221 # Update part.has_fstab here as fstab may have been added or
222 # removed by the above modifications.
223 part.has_fstab = os.path.exists(os.path.join(new_rootfs, "etc/fstab"))
224 if part.update_fstab_in_rootfs and part.has_fstab and not part.no_fstab_update:
225 fstab_path = os.path.join(new_rootfs, "etc/fstab")
226 # Assume that fstab should always be owned by root with fixed permissions
227 install_cmd = "install -m 0644 -p %s %s" % (part.updated_fstab_path, fstab_path)
228 if new_pseudo:
229 pseudo = cls.__get_pseudo(native_sysroot, new_rootfs, new_pseudo)
230 else:
231 pseudo = None
232 exec_native_cmd(install_cmd, native_sysroot, pseudo)
233
234 part.prepare_rootfs(cr_workdir, oe_builddir,
235 new_rootfs or part.rootfs_dir, native_sysroot,
236 pseudo_dir = new_pseudo or pseudo_dir)