xref: /openbmc/openbmc/poky/meta/lib/oeqa/selftest/cases/wic.py (revision c9537f57ab488bf5d90132917b0184e2527970a5)
1#
2# Copyright (c) 2015, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# AUTHORS
7# Ed Bartosh <ed.bartosh@linux.intel.com>
8
9"""Test cases for wic."""
10
11import os
12import sys
13import unittest
14import hashlib
15import subprocess
16
17from glob import glob
18from shutil import rmtree, copy
19from tempfile import NamedTemporaryFile
20from tempfile import TemporaryDirectory
21
22from oeqa.selftest.case import OESelftestTestCase
23from oeqa.core.decorator import OETestTag
24from oeqa.core.decorator.data import skipIfNotArch
25from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
26
27
28def extract_files(debugfs_output):
29    """
30    extract file names from the output of debugfs -R 'ls -p',
31    which looks like this:
32
33     /2/040755/0/0/.//\n
34     /2/040755/0/0/..//\n
35     /11/040700/0/0/lost+found^M//\n
36     /12/040755/1002/1002/run//\n
37     /13/040755/1002/1002/sys//\n
38     /14/040755/1002/1002/bin//\n
39     /80/040755/1002/1002/var//\n
40     /92/040755/1002/1002/tmp//\n
41    """
42    # NOTE the occasional ^M in file names
43    return [line.split('/')[5].strip() for line in \
44            debugfs_output.strip().split('/\n')]
45
46def files_own_by_root(debugfs_output):
47    for line in debugfs_output.strip().split('/\n'):
48        if line.split('/')[3:5] != ['0', '0']:
49            print(debugfs_output)
50            return False
51    return True
52
53class WicTestCase(OESelftestTestCase):
54    """Wic test class."""
55
56    image_is_ready = False
57    wicenv_cache = {}
58
59    def setUpLocal(self):
60        """This code is executed before each test method."""
61        self.resultdir = os.path.join(self.builddir, "wic-tmp")
62        super(WicTestCase, self).setUpLocal()
63
64        # Do this here instead of in setUpClass as the base setUp does some
65        # clean up which can result in the native tools built earlier in
66        # setUpClass being unavailable.
67        if not WicTestCase.image_is_ready:
68            if self.td['USE_NLS'] != 'yes':
69                self.skipTest('wic-tools needs USE_NLS=yes')
70
71            bitbake('wic-tools core-image-minimal core-image-minimal-mtdutils')
72            WicTestCase.image_is_ready = True
73        rmtree(self.resultdir, ignore_errors=True)
74
75    def tearDownLocal(self):
76        """Remove resultdir as it may contain images."""
77        rmtree(self.resultdir, ignore_errors=True)
78        super(WicTestCase, self).tearDownLocal()
79
80    def _get_image_env_path(self, image):
81        """Generate and obtain the path to <image>.env"""
82        if image not in WicTestCase.wicenv_cache:
83            bitbake('%s -c do_rootfs_wicenv' % image)
84            stdir = get_bb_var('STAGING_DIR', image)
85            machine = self.td["MACHINE"]
86            WicTestCase.wicenv_cache[image] = os.path.join(stdir, machine, 'imgdata')
87        return WicTestCase.wicenv_cache[image]
88
89class CLITests(OESelftestTestCase):
90    def test_version(self):
91        """Test wic --version"""
92        runCmd('wic --version')
93
94    def test_help(self):
95        """Test wic --help and wic -h"""
96        runCmd('wic --help')
97        runCmd('wic -h')
98
99    def test_createhelp(self):
100        """Test wic create --help"""
101        runCmd('wic create --help')
102
103    def test_listhelp(self):
104        """Test wic list --help"""
105        runCmd('wic list --help')
106
107    def test_help_create(self):
108        """Test wic help create"""
109        runCmd('wic help create')
110
111    def test_help_list(self):
112        """Test wic help list"""
113        runCmd('wic help list')
114
115    def test_help_overview(self):
116        """Test wic help overview"""
117        runCmd('wic help overview')
118
119    def test_help_plugins(self):
120        """Test wic help plugins"""
121        runCmd('wic help plugins')
122
123    def test_help_kickstart(self):
124        """Test wic help kickstart"""
125        runCmd('wic help kickstart')
126
127    def test_list_images(self):
128        """Test wic list images"""
129        runCmd('wic list images')
130
131    def test_list_source_plugins(self):
132        """Test wic list source-plugins"""
133        runCmd('wic list source-plugins')
134
135    def test_listed_images_help(self):
136        """Test wic listed images help"""
137        output = runCmd('wic list images').output
138        imagelist = [line.split()[0] for line in output.splitlines()]
139        for image in imagelist:
140            runCmd('wic list %s help' % image)
141
142    def test_unsupported_subcommand(self):
143        """Test unsupported subcommand"""
144        self.assertNotEqual(0, runCmd('wic unsupported', ignore_status=True).status)
145
146    def test_no_command(self):
147        """Test wic without command"""
148        self.assertEqual(1, runCmd('wic', ignore_status=True).status)
149
150class Wic(WicTestCase):
151    def test_skip_kernel_install(self):
152        """Test the functionality of not installing the kernel in the boot directory using the wic plugin"""
153        # create a temporary file for the WKS content
154        with NamedTemporaryFile("w", suffix=".wks") as wks:
155            wks.write(
156                'part --source bootimg-efi '
157                '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=false" '
158                '--label boot --active\n'
159            )
160            wks.flush()
161            # create a temporary directory to extract the disk image to
162            with TemporaryDirectory() as tmpdir:
163                img = 'core-image-minimal'
164                # build the image using the WKS file
165                cmd = "wic create %s -e %s -o %s" % (
166                    wks.name, img, self.resultdir)
167                runCmd(cmd)
168                wksname = os.path.splitext(os.path.basename(wks.name))[0]
169                out = glob(os.path.join(
170                    self.resultdir, "%s-*.direct" % wksname))
171                self.assertEqual(1, len(out))
172                sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
173                # extract the content of the disk image to the temporary directory
174                cmd = "wic cp %s:1 %s -n %s" % (out[0], tmpdir, sysroot)
175                runCmd(cmd)
176                # check if the kernel is installed or not
177                kimgtype = get_bb_var('KERNEL_IMAGETYPE', img)
178                for file in os.listdir(tmpdir):
179                    if file == kimgtype:
180                        raise AssertionError(
181                            "The kernel image '{}' was found in the partition".format(kimgtype)
182                        )
183
184    def test_kernel_install(self):
185        """Test the installation of the kernel to the boot directory in the wic plugin"""
186        # create a temporary file for the WKS content
187        with NamedTemporaryFile("w", suffix=".wks") as wks:
188            wks.write(
189                'part --source bootimg-efi '
190                '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=true" '
191                '--label boot --active\n'
192            )
193            wks.flush()
194            # create a temporary directory to extract the disk image to
195            with TemporaryDirectory() as tmpdir:
196                img = 'core-image-minimal'
197                # build the image using the WKS file
198                cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
199                runCmd(cmd)
200                wksname = os.path.splitext(os.path.basename(wks.name))[0]
201                out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
202                self.assertEqual(1, len(out))
203                sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
204                # extract the content of the disk image to the temporary directory
205                cmd = "wic cp %s:1 %s -n %s" % (out[0], tmpdir, sysroot)
206                runCmd(cmd)
207                # check if the kernel is installed or not
208                kimgtype = get_bb_var('KERNEL_IMAGETYPE', img)
209                found = False
210                for file in os.listdir(tmpdir):
211                    if file == kimgtype:
212                        found = True
213                        break
214                self.assertTrue(
215                    found, "The kernel image '{}' was not found in the boot partition".format(kimgtype)
216                )
217
218    def test_build_image_name(self):
219        """Test wic create wictestdisk --image-name=core-image-minimal"""
220        cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir
221        runCmd(cmd)
222        self.assertEqual(1, len(glob(os.path.join (self.resultdir, "wictestdisk-*.direct"))))
223
224    @skipIfNotArch(['i586', 'i686', 'x86_64'])
225    def test_gpt_image(self):
226        """Test creation of core-image-minimal with gpt table and UUID boot"""
227        cmd = "wic create directdisk-gpt --image-name core-image-minimal -o %s" % self.resultdir
228        runCmd(cmd)
229        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-*.direct"))))
230
231    @skipIfNotArch(['i586', 'i686', 'x86_64'])
232    def test_iso_image(self):
233        """Test creation of hybrid iso image with legacy and EFI boot"""
234        config = 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\
235                 'MACHINE_FEATURES:append = " efi"\n'\
236                 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
237        self.append_config(config)
238        bitbake('core-image-minimal core-image-minimal-initramfs')
239        self.remove_config(config)
240        cmd = "wic create mkhybridiso --image-name core-image-minimal -o %s" % self.resultdir
241        runCmd(cmd)
242        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "HYBRID_ISO_IMG-*.direct"))))
243        self.assertEqual(1, len(glob(os.path.join (self.resultdir, "HYBRID_ISO_IMG-*.iso"))))
244
245    @skipIfNotArch(['i586', 'i686', 'x86_64'])
246    def test_qemux86_directdisk(self):
247        """Test creation of qemux-86-directdisk image"""
248        cmd = "wic create qemux86-directdisk -e core-image-minimal -o %s" % self.resultdir
249        runCmd(cmd)
250        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "qemux86-directdisk-*direct"))))
251
252    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
253    def test_mkefidisk(self):
254        """Test creation of mkefidisk image"""
255        cmd = "wic create mkefidisk -e core-image-minimal -o %s" % self.resultdir
256        runCmd(cmd)
257        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "mkefidisk-*direct"))))
258
259    @skipIfNotArch(['i586', 'i686', 'x86_64'])
260    def test_bootloader_config(self):
261        """Test creation of directdisk-bootloader-config image"""
262        config = 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
263        self.append_config(config)
264        bitbake('core-image-minimal')
265        self.remove_config(config)
266        cmd = "wic create directdisk-bootloader-config -e core-image-minimal -o %s" % self.resultdir
267        runCmd(cmd)
268        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-bootloader-config-*direct"))))
269
270    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
271    def test_systemd_bootdisk(self):
272        """Test creation of systemd-bootdisk image"""
273        config = 'MACHINE_FEATURES:append = " efi"\n'
274        self.append_config(config)
275        bitbake('core-image-minimal')
276        self.remove_config(config)
277        cmd = "wic create systemd-bootdisk -e core-image-minimal -o %s" % self.resultdir
278        runCmd(cmd)
279        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "systemd-bootdisk-*direct"))))
280
281    def test_efi_bootpart(self):
282        """Test creation of efi-bootpart image"""
283        cmd = "wic create mkefidisk -e core-image-minimal -o %s" % self.resultdir
284        kimgtype = get_bb_var('KERNEL_IMAGETYPE', 'core-image-minimal')
285        self.append_config('IMAGE_EFI_BOOT_FILES = "%s;kernel"\n' % kimgtype)
286        runCmd(cmd)
287        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
288        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
289        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
290        self.assertIn("kernel",result.output)
291
292    def test_sdimage_bootpart(self):
293        """Test creation of sdimage-bootpart image"""
294        cmd = "wic create sdimage-bootpart -e core-image-minimal -o %s" % self.resultdir
295        kimgtype = get_bb_var('KERNEL_IMAGETYPE', 'core-image-minimal')
296        self.write_config('IMAGE_BOOT_FILES = "%s"\n' % kimgtype)
297        runCmd(cmd)
298        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "sdimage-bootpart-*direct"))))
299
300    # TODO this doesn't have to be x86-specific
301    @skipIfNotArch(['i586', 'i686', 'x86_64'])
302    def test_default_output_dir(self):
303        """Test default output location"""
304        for fname in glob("directdisk-*.direct"):
305            os.remove(fname)
306        config = 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
307        self.append_config(config)
308        bitbake('core-image-minimal')
309        self.remove_config(config)
310        cmd = "wic create directdisk -e core-image-minimal"
311        runCmd(cmd)
312        self.assertEqual(1, len(glob("directdisk-*.direct")))
313
314    @skipIfNotArch(['i586', 'i686', 'x86_64'])
315    def test_build_artifacts(self):
316        """Test wic create directdisk providing all artifacts."""
317        bb_vars = get_bb_vars(['STAGING_DATADIR', 'RECIPE_SYSROOT_NATIVE'],
318                              'wic-tools')
319        bb_vars.update(get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_ROOTFS'],
320                                   'core-image-minimal'))
321        bbvars = {key.lower(): value for key, value in bb_vars.items()}
322        bbvars['resultdir'] = self.resultdir
323        runCmd("wic create directdisk "
324                        "-b %(staging_datadir)s "
325                        "-k %(deploy_dir_image)s "
326                        "-n %(recipe_sysroot_native)s "
327                        "-r %(image_rootfs)s "
328                        "-o %(resultdir)s" % bbvars)
329        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-*.direct"))))
330
331    def test_compress_gzip(self):
332        """Test compressing an image with gzip"""
333        runCmd("wic create wictestdisk "
334                                   "--image-name core-image-minimal "
335                                   "-c gzip -o %s" % self.resultdir)
336        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.gz"))))
337
338    def test_compress_bzip2(self):
339        """Test compressing an image with bzip2"""
340        runCmd("wic create wictestdisk "
341                                   "--image-name=core-image-minimal "
342                                   "-c bzip2 -o %s" % self.resultdir)
343        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.bz2"))))
344
345    def test_compress_xz(self):
346        """Test compressing an image with xz"""
347        runCmd("wic create wictestdisk "
348                                   "--image-name=core-image-minimal "
349                                   "--compress-with=xz -o %s" % self.resultdir)
350        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.xz"))))
351
352    def test_wrong_compressor(self):
353        """Test how wic breaks if wrong compressor is provided"""
354        self.assertEqual(2, runCmd("wic create wictestdisk "
355                                   "--image-name=core-image-minimal "
356                                   "-c wrong -o %s" % self.resultdir,
357                                   ignore_status=True).status)
358
359    def test_debug_short(self):
360        """Test -D option"""
361        runCmd("wic create wictestdisk "
362                                   "--image-name=core-image-minimal "
363                                   "-D -o %s" % self.resultdir)
364        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
365        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "tmp.wic*"))))
366
367    def test_debug_long(self):
368        """Test --debug option"""
369        runCmd("wic create wictestdisk "
370                                   "--image-name=core-image-minimal "
371                                   "--debug -o %s" % self.resultdir)
372        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
373        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "tmp.wic*"))))
374
375    def test_skip_build_check_short(self):
376        """Test -s option"""
377        runCmd("wic create wictestdisk "
378                                   "--image-name=core-image-minimal "
379                                   "-s -o %s" % self.resultdir)
380        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
381
382    def test_skip_build_check_long(self):
383        """Test --skip-build-check option"""
384        runCmd("wic create wictestdisk "
385                                   "--image-name=core-image-minimal "
386                                   "--skip-build-check "
387                                   "--outdir %s" % self.resultdir)
388        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
389
390    def test_build_rootfs_short(self):
391        """Test -f option"""
392        runCmd("wic create wictestdisk "
393                                   "--image-name=core-image-minimal "
394                                   "-f -o %s" % self.resultdir)
395        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
396
397    def test_build_rootfs_long(self):
398        """Test --build-rootfs option"""
399        runCmd("wic create wictestdisk "
400                                   "--image-name=core-image-minimal "
401                                   "--build-rootfs "
402                                   "--outdir %s" % self.resultdir)
403        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
404
405    # TODO this doesn't have to be x86-specific
406    @skipIfNotArch(['i586', 'i686', 'x86_64'])
407    def test_rootfs_indirect_recipes(self):
408        """Test usage of rootfs plugin with rootfs recipes"""
409        runCmd("wic create directdisk-multi-rootfs "
410                        "--image-name=core-image-minimal "
411                        "--rootfs rootfs1=core-image-minimal "
412                        "--rootfs rootfs2=core-image-minimal "
413                        "--outdir %s" % self.resultdir)
414        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-multi-rootfs*.direct"))))
415
416    # TODO this doesn't have to be x86-specific
417    @skipIfNotArch(['i586', 'i686', 'x86_64'])
418    def test_rootfs_artifacts(self):
419        """Test usage of rootfs plugin with rootfs paths"""
420        bb_vars = get_bb_vars(['STAGING_DATADIR', 'RECIPE_SYSROOT_NATIVE'],
421                              'wic-tools')
422        bb_vars.update(get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_ROOTFS'],
423                                   'core-image-minimal'))
424        bbvars = {key.lower(): value for key, value in bb_vars.items()}
425        bbvars['wks'] = "directdisk-multi-rootfs"
426        bbvars['resultdir'] = self.resultdir
427        runCmd("wic create %(wks)s "
428                        "--bootimg-dir=%(staging_datadir)s "
429                        "--kernel-dir=%(deploy_dir_image)s "
430                        "--native-sysroot=%(recipe_sysroot_native)s "
431                        "--rootfs-dir rootfs1=%(image_rootfs)s "
432                        "--rootfs-dir rootfs2=%(image_rootfs)s "
433                        "--outdir %(resultdir)s" % bbvars)
434        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "%(wks)s-*.direct" % bbvars))))
435
436    def test_exclude_path(self):
437        """Test --exclude-path wks option."""
438
439        oldpath = os.environ['PATH']
440        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
441
442        try:
443            wks_file = 'temp.wks'
444            with open(wks_file, 'w') as wks:
445                rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal')
446                wks.write("""
447part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr
448part /usr --source rootfs --ondisk mmcblk0 --fstype=ext4 --rootfs-dir %s/usr
449part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --rootfs-dir %s/usr
450part /mnt --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/whoami --rootfs-dir %s/usr"""
451                          % (rootfs_dir, rootfs_dir, rootfs_dir))
452            runCmd("wic create %s -e core-image-minimal -o %s" \
453                                       % (wks_file, self.resultdir))
454
455            os.remove(wks_file)
456            wicout = glob(os.path.join(self.resultdir, "%s-*direct" % 'temp'))
457            self.assertEqual(1, len(wicout))
458
459            wicimg = wicout[0]
460
461            # verify partition size with wic
462            res = runCmd("parted -m %s unit b p" % wicimg, stderr=subprocess.PIPE)
463
464            # parse parted output which looks like this:
465            # BYT;\n
466            # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
467            # 1:0.00MiB:200MiB:200MiB:ext4::;\n
468            partlns = res.output.splitlines()[2:]
469
470            self.assertEqual(4, len(partlns))
471
472            for part in [1, 2, 3, 4]:
473                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
474                partln = partlns[part-1].split(":")
475                self.assertEqual(7, len(partln))
476                start = int(partln[1].rstrip("B")) / 512
477                length = int(partln[3].rstrip("B")) / 512
478                runCmd("dd if=%s of=%s skip=%d count=%d" %
479                                           (wicimg, part_file, start, length))
480
481            # Test partition 1, should contain the normal root directories, except
482            # /usr.
483            res = runCmd("debugfs -R 'ls -p' %s" % \
484                             os.path.join(self.resultdir, "selftest_img.part1"), stderr=subprocess.PIPE)
485            files = extract_files(res.output)
486            self.assertIn("etc", files)
487            self.assertNotIn("usr", files)
488
489            # Partition 2, should contain common directories for /usr, not root
490            # directories.
491            res = runCmd("debugfs -R 'ls -p' %s" % \
492                             os.path.join(self.resultdir, "selftest_img.part2"), stderr=subprocess.PIPE)
493            files = extract_files(res.output)
494            self.assertNotIn("etc", files)
495            self.assertNotIn("usr", files)
496            self.assertIn("share", files)
497
498            # Partition 3, should contain the same as partition 2, including the bin
499            # directory, but not the files inside it.
500            res = runCmd("debugfs -R 'ls -p' %s" % \
501                             os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
502            files = extract_files(res.output)
503            self.assertNotIn("etc", files)
504            self.assertNotIn("usr", files)
505            self.assertIn("share", files)
506            self.assertIn("bin", files)
507            res = runCmd("debugfs -R 'ls -p bin' %s" % \
508                             os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
509            files = extract_files(res.output)
510            self.assertIn(".", files)
511            self.assertIn("..", files)
512            self.assertEqual(2, len(files))
513
514            # Partition 4, should contain the same as partition 2, including the bin
515            # directory, but not whoami (a symlink to busybox.nosuid) inside it.
516            res = runCmd("debugfs -R 'ls -p' %s" % \
517                             os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
518            files = extract_files(res.output)
519            self.assertNotIn("etc", files)
520            self.assertNotIn("usr", files)
521            self.assertIn("share", files)
522            self.assertIn("bin", files)
523            res = runCmd("debugfs -R 'ls -p bin' %s" % \
524                             os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
525            files = extract_files(res.output)
526            self.assertIn(".", files)
527            self.assertIn("..", files)
528            self.assertIn("who", files)
529            self.assertNotIn("whoami", files)
530
531            for part in [1, 2, 3, 4]:
532                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
533                os.remove(part_file)
534
535        finally:
536            os.environ['PATH'] = oldpath
537
538    def test_exclude_path_with_extra_space(self):
539        """Test having --exclude-path with IMAGE_ROOTFS_EXTRA_SPACE. [Yocto #15555]"""
540
541        with NamedTemporaryFile("w", suffix=".wks") as wks:
542            wks.writelines(
543                ['bootloader --ptable gpt\n',
544                 'part /boot --size=100M --active --fstype=ext4 --label boot\n',
545                 'part /     --source rootfs      --fstype=ext4 --label root --exclude-path boot/\n'])
546            wks.flush()
547            config = 'IMAGE_ROOTFS_EXTRA_SPACE = "500000"\n'\
548                     'DEPENDS:pn-core-image-minimal += "wic-tools"\n'\
549                     'IMAGE_FSTYPES += "wic ext4"\n'\
550                     'WKS_FILE = "%s"\n' % wks.name
551            self.append_config(config)
552            bitbake('core-image-minimal')
553
554        """
555        the output of "wic ls <image>.wic" will look something like:
556            Num     Start        End          Size      Fstype
557             1         17408    136332287    136314880  ext4
558             2     136332288    171464703     35132416  ext4
559        we are looking for the size of partition 2
560        i.e. in this case the number 35,132,416
561        without the fix the size will be around 85,403,648
562        with the fix the size should be around 799,960,064
563        """
564        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'MACHINE'], 'core-image-minimal')
565        deploy_dir = bb_vars['DEPLOY_DIR_IMAGE']
566        machine = bb_vars['MACHINE']
567        nativesysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
568        wicout = glob(os.path.join(deploy_dir, "core-image-minimal-%s.rootfs-*.wic" % machine))[0]
569        size_of_root_partition = int(runCmd("wic ls %s --native-sysroot %s" % (wicout, nativesysroot)).output.split('\n')[2].split()[3])
570        self.assertGreater(size_of_root_partition, 500000000)
571
572    def test_include_path(self):
573        """Test --include-path wks option."""
574
575        oldpath = os.environ['PATH']
576        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
577
578        try:
579            include_path = os.path.join(self.resultdir, 'test-include')
580            os.makedirs(include_path)
581            with open(os.path.join(include_path, 'test-file'), 'w') as t:
582                t.write("test\n")
583            wks_file = os.path.join(include_path, 'temp.wks')
584            with open(wks_file, 'w') as wks:
585                rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal')
586                wks.write("""
587part /part1 --source rootfs --ondisk mmcblk0 --fstype=ext4
588part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s"""
589                          % (include_path))
590            runCmd("wic create %s -e core-image-minimal -o %s" \
591                                       % (wks_file, self.resultdir))
592
593            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
594            part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0]
595
596            # Test partition 1, should not contain 'test-file'
597            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
598            files = extract_files(res.output)
599            self.assertNotIn('test-file', files)
600            self.assertEqual(True, files_own_by_root(res.output))
601
602            # Test partition 2, should contain 'test-file'
603            res = runCmd("debugfs -R 'ls -p' %s" % (part2), stderr=subprocess.PIPE)
604            files = extract_files(res.output)
605            self.assertIn('test-file', files)
606            self.assertEqual(True, files_own_by_root(res.output))
607
608        finally:
609            os.environ['PATH'] = oldpath
610
611    def test_include_path_embeded(self):
612        """Test --include-path wks option."""
613
614        oldpath = os.environ['PATH']
615        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
616
617        try:
618            include_path = os.path.join(self.resultdir, 'test-include')
619            os.makedirs(include_path)
620            with open(os.path.join(include_path, 'test-file'), 'w') as t:
621                t.write("test\n")
622            wks_file = os.path.join(include_path, 'temp.wks')
623            with open(wks_file, 'w') as wks:
624                wks.write("""
625part / --source rootfs  --fstype=ext4 --include-path %s --include-path core-image-minimal-mtdutils export/"""
626                          % (include_path))
627            runCmd("wic create %s -e core-image-minimal -o %s" \
628                                       % (wks_file, self.resultdir))
629
630            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
631
632            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
633            files = extract_files(res.output)
634            self.assertIn('test-file', files)
635            self.assertEqual(True, files_own_by_root(res.output))
636
637            res = runCmd("debugfs -R 'ls -p /export/etc/' %s" % (part1), stderr=subprocess.PIPE)
638            files = extract_files(res.output)
639            self.assertIn('passwd', files)
640            self.assertEqual(True, files_own_by_root(res.output))
641
642        finally:
643            os.environ['PATH'] = oldpath
644
645    def test_include_path_errors(self):
646        """Test --include-path wks option error handling."""
647        wks_file = 'temp.wks'
648
649        # Absolute argument.
650        with open(wks_file, 'w') as wks:
651            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils /export")
652        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
653                                      % (wks_file, self.resultdir), ignore_status=True).status)
654        os.remove(wks_file)
655
656        # Argument pointing to parent directory.
657        with open(wks_file, 'w') as wks:
658            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils ././..")
659        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
660                                      % (wks_file, self.resultdir), ignore_status=True).status)
661        os.remove(wks_file)
662
663        # 3 Argument pointing to parent directory.
664        with open(wks_file, 'w') as wks:
665            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils export/ dummy")
666        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
667                                      % (wks_file, self.resultdir), ignore_status=True).status)
668        os.remove(wks_file)
669
670    def test_exclude_path_errors(self):
671        """Test --exclude-path wks option error handling."""
672        wks_file = 'temp.wks'
673
674        # Absolute argument.
675        with open(wks_file, 'w') as wks:
676            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path /usr")
677        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
678                                      % (wks_file, self.resultdir), ignore_status=True).status)
679        os.remove(wks_file)
680
681        # Argument pointing to parent directory.
682        with open(wks_file, 'w') as wks:
683            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path ././..")
684        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
685                                      % (wks_file, self.resultdir), ignore_status=True).status)
686        os.remove(wks_file)
687
688    def test_permissions(self):
689        """Test permissions are respected"""
690
691        # prepare wicenv and rootfs
692        bitbake('core-image-minimal core-image-minimal-mtdutils -c do_rootfs_wicenv')
693
694        oldpath = os.environ['PATH']
695        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
696
697        t_normal = """
698part / --source rootfs --fstype=ext4
699"""
700        t_exclude = """
701part / --source rootfs --fstype=ext4 --exclude-path=home
702"""
703        t_multi = """
704part / --source rootfs --ondisk sda --fstype=ext4
705part /export --source rootfs --rootfs=core-image-minimal-mtdutils --fstype=ext4
706"""
707        t_change = """
708part / --source rootfs --ondisk sda --fstype=ext4 --exclude-path=etc/   
709part /etc --source rootfs --fstype=ext4 --change-directory=etc
710"""
711        tests = [t_normal, t_exclude, t_multi, t_change]
712
713        try:
714            for test in tests:
715                include_path = os.path.join(self.resultdir, 'test-include')
716                os.makedirs(include_path)
717                wks_file = os.path.join(include_path, 'temp.wks')
718                with open(wks_file, 'w') as wks:
719                    wks.write(test)
720                runCmd("wic create %s -e core-image-minimal -o %s" \
721                                       % (wks_file, self.resultdir))
722
723                for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')):
724                    res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
725                    self.assertEqual(True, files_own_by_root(res.output))
726
727                config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file
728                self.append_config(config)
729                bitbake('core-image-minimal')
730                tmpdir = os.path.join(get_bb_var('WORKDIR', 'core-image-minimal'),'build-wic')
731
732                # check each partition for permission
733                for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')):
734                    res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
735                    self.assertTrue(files_own_by_root(res.output)
736                        ,msg='Files permission incorrect using wks set "%s"' % test)
737
738                # clean config and result directory for next cases
739                self.remove_config(config)
740                rmtree(self.resultdir, ignore_errors=True)
741
742        finally:
743            os.environ['PATH'] = oldpath
744
745    def test_change_directory(self):
746        """Test --change-directory wks option."""
747
748        oldpath = os.environ['PATH']
749        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
750
751        try:
752            include_path = os.path.join(self.resultdir, 'test-include')
753            os.makedirs(include_path)
754            wks_file = os.path.join(include_path, 'temp.wks')
755            with open(wks_file, 'w') as wks:
756                wks.write("part /etc --source rootfs --fstype=ext4 --change-directory=etc")
757            runCmd("wic create %s -e core-image-minimal -o %s" \
758                                       % (wks_file, self.resultdir))
759
760            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
761
762            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
763            files = extract_files(res.output)
764            self.assertIn('passwd', files)
765
766        finally:
767            os.environ['PATH'] = oldpath
768
769    def test_change_directory_errors(self):
770        """Test --change-directory wks option error handling."""
771        wks_file = 'temp.wks'
772
773        # Absolute argument.
774        with open(wks_file, 'w') as wks:
775            wks.write("part / --source rootfs --fstype=ext4 --change-directory /usr")
776        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
777                                      % (wks_file, self.resultdir), ignore_status=True).status)
778        os.remove(wks_file)
779
780        # Argument pointing to parent directory.
781        with open(wks_file, 'w') as wks:
782            wks.write("part / --source rootfs --fstype=ext4 --change-directory ././..")
783        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
784                                      % (wks_file, self.resultdir), ignore_status=True).status)
785        os.remove(wks_file)
786
787    def test_no_fstab_update(self):
788        """Test --no-fstab-update wks option."""
789
790        oldpath = os.environ['PATH']
791        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
792
793        # Get stock fstab from base-files recipe
794        bitbake('base-files -c do_install')
795        bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab')
796        self.assertEqual(True, os.path.exists(bf_fstab))
797        bf_fstab_md5sum = runCmd('md5sum %s ' % bf_fstab).output.split(" ")[0]
798
799        try:
800            no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update')
801            os.makedirs(no_fstab_update_path)
802            wks_file = os.path.join(no_fstab_update_path, 'temp.wks')
803            with open(wks_file, 'w') as wks:
804                wks.writelines(['part / --source rootfs --fstype=ext4 --label rootfs\n',
805                                'part /mnt/p2 --source rootfs --rootfs-dir=core-image-minimal ',
806                                '--fstype=ext4 --label p2 --no-fstab-update\n'])
807            runCmd("wic create %s -e core-image-minimal -o %s" \
808                                       % (wks_file, self.resultdir))
809
810            part_fstab_md5sum = []
811            for i in range(1, 3):
812                part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0]
813                part_fstab = runCmd("debugfs -R 'cat etc/fstab' %s" % (part), stderr=subprocess.PIPE)
814                part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest())
815
816            # '/etc/fstab' in partition 2 should contain the same stock fstab file
817            # as the one installed by the base-file recipe.
818            self.assertEqual(bf_fstab_md5sum, part_fstab_md5sum[1])
819
820            # '/etc/fstab' in partition 1 should contain an updated fstab file.
821            self.assertNotEqual(bf_fstab_md5sum, part_fstab_md5sum[0])
822
823        finally:
824            os.environ['PATH'] = oldpath
825
826    def test_no_fstab_update_errors(self):
827        """Test --no-fstab-update wks option error handling."""
828        wks_file = 'temp.wks'
829
830        # Absolute argument.
831        with open(wks_file, 'w') as wks:
832            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update /etc")
833        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
834                                      % (wks_file, self.resultdir), ignore_status=True).status)
835        os.remove(wks_file)
836
837        # Argument pointing to parent directory.
838        with open(wks_file, 'w') as wks:
839            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update ././..")
840        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
841                                      % (wks_file, self.resultdir), ignore_status=True).status)
842        os.remove(wks_file)
843
844    def test_extra_space(self):
845        """Test --extra-space wks option."""
846        extraspace = 1024**3
847        runCmd("wic create wictestdisk "
848                                   "--image-name core-image-minimal "
849                                   "--extra-space %i -o %s" % (extraspace ,self.resultdir))
850        wicout = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
851        self.assertEqual(1, len(wicout))
852        size = os.path.getsize(wicout[0])
853        self.assertTrue(size > extraspace, msg="Extra space not present (%s vs %s)" % (size, extraspace))
854
855    def test_no_table(self):
856        """Test --no-table wks option."""
857        wks_file = 'temp.wks'
858
859        # Absolute argument.
860        with open(wks_file, 'w') as wks:
861            wks.write("part testspace --no-table --fixed-size 16k --offset 4080k")
862        runCmd("wic create %s --image-name core-image-minimal -o %s" % (wks_file, self.resultdir))
863
864        wicout = glob(os.path.join(self.resultdir, "*.*"))
865
866        self.assertEqual(1, len(wicout))
867        size = os.path.getsize(wicout[0])
868        self.assertEqual(size, 4 * 1024 * 1024)
869
870        os.remove(wks_file)
871
872    def test_partition_hidden_attributes(self):
873        """Test --hidden wks option."""
874        wks_file = 'temp.wks'
875        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
876        try:
877            with open(wks_file, 'w') as wks:
878                wks.write("""
879part / --source rootfs --fstype=ext4
880part / --source rootfs --fstype=ext4 --hidden
881bootloader --ptable gpt""")
882
883            runCmd("wic create %s -e core-image-minimal -o %s" \
884                                       % (wks_file, self.resultdir))
885            wicout = os.path.join(self.resultdir, "*.direct")
886
887            result = runCmd("%s/usr/sbin/sfdisk --part-attrs %s 1" % (sysroot, wicout))
888            self.assertEqual('', result.output)
889            result = runCmd("%s/usr/sbin/sfdisk --part-attrs %s 2" % (sysroot, wicout))
890            self.assertEqual('RequiredPartition', result.output)
891
892        finally:
893            os.remove(wks_file)
894
895    def test_wic_sector_size(self):
896        """Test generation image sector size"""
897
898        oldpath = os.environ['PATH']
899        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
900
901        try:
902            # Add WIC_SECTOR_SIZE into config
903            config = 'WIC_SECTOR_SIZE = "4096"\n'\
904                     'WICVARS:append = " WIC_SECTOR_SIZE"\n'
905            self.append_config(config)
906            bitbake('core-image-minimal')
907
908            # Check WIC_SECTOR_SIZE apply to bitbake variable
909            wic_sector_size_str = get_bb_var('WIC_SECTOR_SIZE', 'core-image-minimal')
910            wic_sector_size = int(wic_sector_size_str)
911            self.assertEqual(4096, wic_sector_size)
912
913            self.logger.info("Test wic_sector_size: %d \n" % wic_sector_size)
914
915            with NamedTemporaryFile("w", suffix=".wks") as wks:
916                wks.writelines(
917                    ['bootloader --ptable gpt\n',
918                     'part --fstype ext4 --source rootfs --label rofs-a --mkfs-extraopts "-b 4096"\n',
919                     'part --fstype ext4 --source rootfs --use-uuid --mkfs-extraopts "-b 4096"\n'])
920                wks.flush()
921                cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
922                runCmd(cmd)
923                wksname = os.path.splitext(os.path.basename(wks.name))[0]
924                images = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
925                self.assertEqual(1, len(images))
926
927            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
928            # list partitions
929            result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
930            self.assertEqual(3, len(result.output.split('\n')))
931
932            # verify partition size with wic
933            res = runCmd("export PARTED_SECTOR_SIZE=%d; parted -m %s unit b p" % (wic_sector_size, images[0]),
934                         stderr=subprocess.PIPE)
935
936            # parse parted output which looks like this:
937            # BYT;\n
938            # /var/tmp/wic/build/tmpgjzzefdd-202410281021-sda.direct:78569472B:file:4096:4096:gpt::;\n
939            # 1:139264B:39284735B:39145472B:ext4:rofs-a:;\n
940            # 2:39284736B:78430207B:39145472B:ext4:primary:;\n
941            disk_info = res.output.splitlines()[1]
942            # Check sector sizes
943            sector_size_logical = int(disk_info.split(":")[3])
944            sector_size_physical = int(disk_info.split(":")[4])
945            self.assertEqual(wic_sector_size, sector_size_logical, "Logical sector size is not %d." % wic_sector_size)
946            self.assertEqual(wic_sector_size, sector_size_physical, "Physical sector size is not %d." % wic_sector_size)
947
948        finally:
949            os.environ['PATH'] = oldpath
950
951class Wic2(WicTestCase):
952
953    def test_bmap_short(self):
954        """Test generation of .bmap file -m option"""
955        cmd = "wic create wictestdisk -e core-image-minimal -m -o %s" % self.resultdir
956        runCmd(cmd)
957        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
958        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
959
960    def test_bmap_long(self):
961        """Test generation of .bmap file --bmap option"""
962        cmd = "wic create wictestdisk -e core-image-minimal --bmap -o %s" % self.resultdir
963        runCmd(cmd)
964        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
965        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
966
967    def test_image_env(self):
968        """Test generation of <image>.env files."""
969        image = 'core-image-minimal'
970        imgdatadir = self._get_image_env_path(image)
971
972        bb_vars = get_bb_vars(['IMAGE_BASENAME', 'WICVARS'], image)
973        basename = bb_vars['IMAGE_BASENAME']
974        self.assertEqual(basename, image)
975        path = os.path.join(imgdatadir, basename) + '.env'
976        self.assertTrue(os.path.isfile(path), msg="File %s wasn't generated as expected" % path)
977
978        wicvars = set(bb_vars['WICVARS'].split())
979        # filter out optional variables
980        wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES',
981                                      'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE',
982                                      'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME',
983                                      'APPEND', 'IMAGE_EFI_BOOT_FILES'))
984        with open(path) as envfile:
985            content = dict(line.split("=", 1) for line in envfile)
986            # test if variables used by wic present in the .env file
987            for var in wicvars:
988                self.assertTrue(var in content, "%s is not in .env file" % var)
989                self.assertTrue(content[var], "%s doesn't have a value (%s)" % (var, content[var]))
990
991    def test_image_vars_dir_short(self):
992        """Test image vars directory selection -v option"""
993        image = 'core-image-minimal'
994        imgenvdir = self._get_image_env_path(image)
995        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
996
997        runCmd("wic create wictestdisk "
998                                   "--image-name=%s -v %s -n %s -o %s"
999                                   % (image, imgenvdir, native_sysroot,
1000                                      self.resultdir))
1001        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
1002
1003    def test_image_vars_dir_long(self):
1004        """Test image vars directory selection --vars option"""
1005        image = 'core-image-minimal'
1006        imgenvdir = self._get_image_env_path(image)
1007        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1008
1009        runCmd("wic create wictestdisk "
1010                                   "--image-name=%s "
1011                                   "--vars %s "
1012                                   "--native-sysroot %s "
1013                                   "--outdir %s"
1014                                   % (image, imgenvdir, native_sysroot,
1015                                      self.resultdir))
1016        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
1017
1018    # TODO this test could also work on aarch64
1019    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1020    def test_wic_image_type(self):
1021        """Test building wic images by bitbake"""
1022        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
1023                 'MACHINE_FEATURES:append = " efi"\n'
1024        self.append_config(config)
1025        image = 'wic-image-minimal'
1026        bitbake(image)
1027        self.remove_config(config)
1028
1029        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1030        prefix = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.' % bb_vars['IMAGE_LINK_NAME'])
1031
1032        # check if we have result image and manifests symlinks
1033        # pointing to existing files
1034        for suffix in ('wic', 'manifest'):
1035            path = prefix + suffix
1036            self.assertTrue(os.path.islink(path), msg="Link %s wasn't generated as expected" % path)
1037            self.assertTrue(os.path.isfile(os.path.realpath(path)), msg="File linked to by %s wasn't generated as expected" % path)
1038
1039    # TODO this should work on aarch64
1040    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1041    @OETestTag("runqemu")
1042    def test_qemu(self):
1043        """Test wic-image-minimal under qemu"""
1044        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
1045                 'MACHINE_FEATURES:append = " efi"\n'
1046        self.append_config(config)
1047        bitbake('wic-image-minimal')
1048        self.remove_config(config)
1049
1050        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'wic-image-minimal') or ""
1051        with runqemu('wic-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1052            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' " \
1053                  "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'"
1054            status, output = qemu.run_serial(cmd)
1055            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1056            self.assertEqual(output, '4')
1057            cmd = "grep UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba /etc/fstab"
1058            status, output = qemu.run_serial(cmd)
1059            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1060            self.assertEqual(output, 'UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba\t/media\text4\tdefaults\t0\t0')
1061
1062    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1063    @OETestTag("runqemu")
1064    def test_qemu_efi(self):
1065        """Test core-image-minimal efi image under qemu"""
1066        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "mkefidisk.wks"\n'
1067        self.append_config(config)
1068        bitbake('core-image-minimal ovmf')
1069        self.remove_config(config)
1070
1071        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1072        with runqemu('core-image-minimal', ssh=False,
1073                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1074            cmd = "grep sda. /proc/partitions  |wc -l"
1075            status, output = qemu.run_serial(cmd)
1076            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1077            self.assertEqual(output, '3')
1078
1079    @staticmethod
1080    def _make_fixed_size_wks(size):
1081        """
1082        Create a wks of an image with a single partition. Size of the partition is set
1083        using --fixed-size flag. Returns a tuple: (path to wks file, wks image name)
1084        """
1085        with NamedTemporaryFile("w", suffix=".wks", delete=False) as tempf:
1086            wkspath = tempf.name
1087            tempf.write("part " \
1088                     "--source rootfs --ondisk hda --align 4 --fixed-size %d "
1089                     "--fstype=ext4\n" % size)
1090
1091        return wkspath
1092
1093    def _get_wic_partitions(self, wkspath, native_sysroot=None, ignore_status=False):
1094        p = runCmd("wic create %s -e core-image-minimal -o %s" % (wkspath, self.resultdir),
1095                   ignore_status=ignore_status)
1096
1097        if p.status:
1098            return (p, None)
1099
1100        wksname = os.path.splitext(os.path.basename(wkspath))[0]
1101
1102        wicout = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1103
1104        if not wicout:
1105            return (p, None)
1106
1107        wicimg = wicout[0]
1108
1109        if not native_sysroot:
1110            native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1111
1112        # verify partition size with wic
1113        res = runCmd("parted -m %s unit kib p" % wicimg,
1114                     native_sysroot=native_sysroot, stderr=subprocess.PIPE)
1115
1116        # parse parted output which looks like this:
1117        # BYT;\n
1118        # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
1119        # 1:0.00MiB:200MiB:200MiB:ext4::;\n
1120        return (p, res.output.splitlines()[2:])
1121
1122    def test_fixed_size(self):
1123        """
1124        Test creation of a simple image with partition size controlled through
1125        --fixed-size flag
1126        """
1127        wkspath = Wic2._make_fixed_size_wks(200)
1128        _, partlns = self._get_wic_partitions(wkspath)
1129        os.remove(wkspath)
1130
1131        self.assertEqual(partlns, [
1132                        "1:4.00kiB:204804kiB:204800kiB:ext4::;",
1133                        ])
1134
1135    def test_fixed_size_error(self):
1136        """
1137        Test creation of a simple image with partition size controlled through
1138        --fixed-size flag. The size of partition is intentionally set to 1MiB
1139        in order to trigger an error in wic.
1140        """
1141        wkspath = Wic2._make_fixed_size_wks(1)
1142        p, _ = self._get_wic_partitions(wkspath, ignore_status=True)
1143        os.remove(wkspath)
1144
1145        self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1146
1147    def test_offset(self):
1148        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1149
1150        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1151            # Test that partitions are placed at the correct offsets, default KB
1152            tempf.write("bootloader --ptable gpt\n" \
1153                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 200M --fstype=ext4\n" \
1154                        "part /bar                 --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1155            tempf.flush()
1156
1157            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1158            self.assertEqual(partlns, [
1159                "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1160                "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1161                ])
1162
1163        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1164            # Test that partitions are placed at the correct offsets, same with explicit KB
1165            tempf.write("bootloader --ptable gpt\n" \
1166                        "part /    --source rootfs --ondisk hda --offset 32K     --fixed-size 200M --fstype=ext4\n" \
1167                        "part /bar                 --ondisk hda --offset 204832K --fixed-size 100M --fstype=ext4\n")
1168            tempf.flush()
1169
1170            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1171            self.assertEqual(partlns, [
1172                "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1173                "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1174                ])
1175
1176        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1177            # Test that partitions are placed at the correct offsets using MB
1178            tempf.write("bootloader --ptable gpt\n" \
1179                        "part /    --source rootfs --ondisk hda --offset 32K  --fixed-size 200M --fstype=ext4\n" \
1180                        "part /bar                 --ondisk hda --offset 201M --fixed-size 100M --fstype=ext4\n")
1181            tempf.flush()
1182
1183            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1184            self.assertEqual(partlns, [
1185                "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1186                "2:205824kiB:308224kiB:102400kiB:ext4:primary:;",
1187                ])
1188
1189        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1190            # Test that partitions can be placed on a 512 byte sector boundary
1191            tempf.write("bootloader --ptable gpt\n" \
1192                        "part /    --source rootfs --ondisk hda --offset 65s --fixed-size 199M --fstype=ext4\n" \
1193                        "part /bar                 --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1194            tempf.flush()
1195
1196            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1197            self.assertEqual(partlns, [
1198                "1:32.5kiB:203808kiB:203776kiB:ext4:primary:;",
1199                "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1200                ])
1201
1202        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1203            # Test that a partition can be placed immediately after a MSDOS partition table
1204            tempf.write("bootloader --ptable msdos\n" \
1205                        "part /    --source rootfs --ondisk hda --offset 1s --fixed-size 200M --fstype=ext4\n")
1206            tempf.flush()
1207
1208            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1209            self.assertEqual(partlns, [
1210                "1:0.50kiB:204800kiB:204800kiB:ext4::;",
1211                ])
1212
1213        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1214            # Test that image creation fails if the partitions would overlap
1215            tempf.write("bootloader --ptable gpt\n" \
1216                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 200M --fstype=ext4\n" \
1217                        "part /bar                 --ondisk hda --offset 204831 --fixed-size 100M --fstype=ext4\n")
1218            tempf.flush()
1219
1220            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1221            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1222
1223        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1224            # Test that partitions are not allowed to overlap with the booloader
1225            tempf.write("bootloader --ptable gpt\n" \
1226                        "part /    --source rootfs --ondisk hda --offset 8 --fixed-size 200M --fstype=ext4\n")
1227            tempf.flush()
1228
1229            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1230            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1231
1232    def test_extra_space(self):
1233        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1234
1235        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1236            tempf.write("bootloader --ptable gpt\n" \
1237                        "part /     --source rootfs --ondisk hda --extra-space 200M --fstype=ext4\n")
1238            tempf.flush()
1239
1240            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1241            self.assertEqual(len(partlns), 1)
1242            size = partlns[0].split(':')[3]
1243            self.assertRegex(size, r'^[0-9]+kiB$')
1244            size = int(size[:-3])
1245            self.assertGreaterEqual(size, 204800)
1246
1247    # TODO this test could also work on aarch64
1248    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1249    @OETestTag("runqemu")
1250    def test_rawcopy_plugin_qemu(self):
1251        """Test rawcopy plugin in qemu"""
1252        # build ext4 and then use it for a wic image
1253        config = 'IMAGE_FSTYPES = "ext4"\n'
1254        self.append_config(config)
1255        bitbake('core-image-minimal')
1256        image_link_name = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
1257        self.remove_config(config)
1258
1259        config = 'IMAGE_FSTYPES = "wic"\n' \
1260                 'IMAGE_LINK_NAME_CORE_IMAGE_MINIMAL = "%s"\n'\
1261                 'WKS_FILE = "test_rawcopy_plugin.wks.in"\n'\
1262                 % image_link_name
1263        self.append_config(config)
1264        bitbake('core-image-minimal-mtdutils')
1265        self.remove_config(config)
1266
1267        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal-mtdutils') or ""
1268        with runqemu('core-image-minimal-mtdutils', ssh=False,
1269                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1270            cmd = "grep sda. /proc/partitions  |wc -l"
1271            status, output = qemu.run_serial(cmd)
1272            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1273            self.assertEqual(output, '2')
1274
1275    def _rawcopy_plugin(self, fstype):
1276        """Test rawcopy plugin"""
1277        image = 'core-image-minimal'
1278        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1279        params = ',unpack' if fstype.endswith('.gz') else ''
1280        with NamedTemporaryFile("w", suffix=".wks") as wks:
1281            wks.write('part / --source rawcopy --sourceparams="file=%s.%s%s"\n'\
1282                      % (bb_vars['IMAGE_LINK_NAME'], fstype, params))
1283            wks.flush()
1284            cmd = "wic create %s -e %s -o %s" % (wks.name, image, self.resultdir)
1285            runCmd(cmd)
1286            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1287            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1288            self.assertEqual(1, len(out))
1289
1290    def test_rawcopy_plugin(self):
1291        config = 'IMAGE_FSTYPES = "ext4"\n'
1292        self.append_config(config)
1293        self.assertEqual(0, bitbake('core-image-minimal').status)
1294        self.remove_config(config)
1295        self._rawcopy_plugin('ext4')
1296
1297    def test_rawcopy_plugin_unpack(self):
1298        fstype = 'ext4.gz'
1299        config = 'IMAGE_FSTYPES = "%s"\n' % fstype
1300        self.append_config(config)
1301        self.assertEqual(0, bitbake('core-image-minimal').status)
1302        self.remove_config(config)
1303        self._rawcopy_plugin(fstype)
1304
1305    def test_empty_plugin(self):
1306        """Test empty plugin"""
1307        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_empty_plugin.wks"\n'
1308        self.append_config(config)
1309        image = 'core-image-minimal'
1310        bitbake(image)
1311        self.remove_config(config)
1312        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1313        image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.wic' % bb_vars['IMAGE_LINK_NAME'])
1314        self.assertTrue(os.path.exists(image_path), msg="Image file %s wasn't generated as expected" % image_path)
1315
1316        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1317
1318        # Fstype column from 'wic ls' should be empty for the second partition
1319        # as listed in test_empty_plugin.wks
1320        result = runCmd("wic ls %s -n %s | awk -F ' ' '{print $1 \" \" $5}' | grep '^2' | wc -w" % (image_path, sysroot))
1321        self.assertEqual('1', result.output)
1322
1323    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1324    @OETestTag("runqemu")
1325    def test_biosplusefi_plugin_qemu(self):
1326        """Test biosplusefi plugin in qemu"""
1327        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1328        self.append_config(config)
1329        bitbake('core-image-minimal')
1330        self.remove_config(config)
1331
1332        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1333        with runqemu('core-image-minimal', ssh=False,
1334                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1335            # Check that we have ONLY two /dev/sda* partitions (/boot and /)
1336            cmd = "grep sda. /proc/partitions | wc -l"
1337            status, output = qemu.run_serial(cmd)
1338            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1339            self.assertEqual(output, '2')
1340            # Check that /dev/sda1 is /boot and that either /dev/root OR /dev/sda2 is /
1341            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' -e '/dev/root /|/dev/sda2 /'"
1342            status, output = qemu.run_serial(cmd)
1343            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1344            self.assertEqual(output, '2')
1345            # Check that /boot has EFI bootx64.efi (required for EFI)
1346            cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l"
1347            status, output = qemu.run_serial(cmd)
1348            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1349            self.assertEqual(output, '1')
1350            # Check that "BOOTABLE" flag is set on boot partition (required for PC-Bios)
1351            # Trailing "cat" seems to be required; otherwise run_serial() sends back echo of the input command
1352            cmd = "fdisk -l /dev/sda | grep /dev/sda1 | awk {print'$2'} | cat"
1353            status, output = qemu.run_serial(cmd)
1354            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1355            self.assertEqual(output, '*')
1356
1357    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1358    def test_biosplusefi_plugin(self):
1359        """Test biosplusefi plugin"""
1360        # Wic generation below may fail depending on the order of the unittests
1361        # This is because bootimg-pcbios (that bootimg-biosplusefi uses) generate its MBR inside STAGING_DATADIR directory
1362        #    which may or may not exists depending on what was built already
1363        # If an image hasn't been built yet, directory ${STAGING_DATADIR}/syslinux won't exists and _get_bootimg_dir()
1364        #   will raise with "Couldn't find correct bootimg_dir"
1365        # The easiest way to work-around this issue is to make sure we already built an image here, hence the bitbake call
1366        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1367        self.append_config(config)
1368        bitbake('core-image-minimal')
1369        self.remove_config(config)
1370
1371        img = 'core-image-minimal'
1372        with NamedTemporaryFile("w", suffix=".wks") as wks:
1373            wks.writelines(['part /boot --active --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\n',
1374                            'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1375                            'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1376            wks.flush()
1377            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1378            runCmd(cmd)
1379            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1380            out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
1381            self.assertEqual(1, len(out))
1382
1383    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
1384    def test_uefi_kernel(self):
1385        """ Test uefi-kernel in wic """
1386        config = 'IMAGE_EFI_BOOT_FILES="/etc/fstab;testfile"\nIMAGE_FSTYPES = "wic"\nWKS_FILE = "test_uefikernel.wks"\nMACHINE_FEATURES:append = " efi"\n'
1387        self.append_config(config)
1388        bitbake('core-image-minimal')
1389        self.remove_config(config)
1390
1391        img = 'core-image-minimal'
1392        with NamedTemporaryFile("w", suffix=".wks") as wks:
1393            wks.writelines(['part /boot --source bootimg-efi --sourceparams="loader=uefi-kernel"\n'
1394                            'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1395                            'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1396            wks.flush()
1397            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1398            runCmd(cmd)
1399            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1400            out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
1401            self.assertEqual(1, len(out))
1402
1403    # TODO this test could also work on aarch64
1404    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1405    @OETestTag("runqemu")
1406    def test_efi_plugin_unified_kernel_image_qemu(self):
1407        """Test Unified Kernel Image feature in qemu without systemd in initramfs or rootfs"""
1408        config = """
1409# efi firmware must load systemd-boot, not grub
1410EFI_PROVIDER = "systemd-boot"
1411
1412# image format must be wic, needs esp partition for firmware etc
1413IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1414WKS_FILE = "test_efi_plugin.wks"
1415
1416# efi, uki and systemd features must be enabled
1417MACHINE_FEATURES:append = " efi"
1418IMAGE_CLASSES:append:pn-core-image-base = " uki"
1419
1420# uki embeds also an initrd, no systemd or udev
1421INITRAMFS_IMAGE = "core-image-initramfs-boot"
1422
1423# runqemu must not load kernel separately, it's in the uki
1424QB_KERNEL_ROOT = ""
1425QB_DEFAULT_KERNEL = "none"
1426
1427# boot command line provided via uki, not via bootloader
1428UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
1429
1430"""
1431        self.append_config(config)
1432        bitbake('core-image-base ovmf')
1433        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1434        uki_filename = get_bb_var('UKI_FILENAME', 'core-image-base')
1435        self.remove_config(config)
1436
1437        with runqemu('core-image-base', ssh=False,
1438                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1439            # Check that /boot has EFI boot*.efi (required for EFI)
1440            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1441            status, output = qemu.run_serial(cmd)
1442            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1443            self.assertEqual(output, '1')
1444            # Check that /boot has EFI/Linux/${UKI_FILENAME} (required for Unified Kernel Images auto detection)
1445            cmd = "ls /boot/EFI/Linux/%s | wc -l" % (uki_filename)
1446            status, output = qemu.run_serial(cmd)
1447            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1448            self.assertEqual(output, '1')
1449            # Check that /boot doesn't have loader/entries/boot.conf (Unified Kernel Images are auto detected by the bootloader)
1450            cmd = "ls /boot/loader/entries/boot.conf 2&>/dev/null | wc -l"
1451            status, output = qemu.run_serial(cmd)
1452            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1453            self.assertEqual(output, '0')
1454
1455    @skipIfNotArch(['aarch64'])
1456    @OETestTag("runqemu")
1457    def test_efi_plugin_plain_systemd_boot_qemu_aarch64(self):
1458        """Test plain systemd-boot in qemu with systemd"""
1459        config = """
1460INIT_MANAGER = "systemd"
1461EFI_PROVIDER = "systemd-boot"
1462
1463# image format must be wic, needs esp partition for firmware etc
1464IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1465WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1466
1467INITRAMFS_IMAGE = "core-image-initramfs-boot"
1468
1469# to configure runqemu
1470IMAGE_CLASSES += "qemuboot"
1471# u-boot efi firmware
1472QB_DEFAULT_BIOS = "u-boot.bin"
1473# need to use virtio, scsi not supported by u-boot by default
1474QB_DRIVE_TYPE = "/dev/vd"
1475
1476# disable kvm, breaks boot
1477QEMU_USE_KVM = ""
1478
1479IMAGE_CLASSES:remove = 'testimage'
1480"""
1481        self.append_config(config)
1482        bitbake('core-image-base u-boot')
1483        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1484
1485        with runqemu('core-image-base', ssh=False,
1486                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1487            # Check that /boot has EFI boot*.efi (required for EFI)
1488            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1489            status, output = qemu.run_serial(cmd)
1490            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1491            self.assertEqual(output, '1')
1492            # Check that boot.conf exists
1493            cmd = "cat /boot/loader/entries/boot.conf"
1494            status, output = qemu.run_serial(cmd)
1495            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1496        self.remove_config(config)
1497
1498    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1499    @OETestTag("runqemu")
1500    def test_efi_plugin_plain_systemd_boot_qemu_x86(self):
1501        """Test plain systemd-boot to systemd in qemu"""
1502        config = """
1503INIT_MANAGER = "systemd"
1504EFI_PROVIDER = "systemd-boot"
1505
1506# image format must be wic, needs esp partition for firmware etc
1507IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1508WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1509
1510INITRAMFS_IMAGE = "core-image-initramfs-boot"
1511"""
1512        self.append_config(config)
1513        bitbake('core-image-base ovmf')
1514        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1515        self.remove_config(config)
1516
1517        with runqemu('core-image-base', ssh=False,
1518                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1519            # Check that /boot has EFI boot*.efi (required for EFI)
1520            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1521            status, output = qemu.run_serial(cmd)
1522            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1523            self.assertEqual(output, '1')
1524            # Check that boot.conf exists
1525            cmd = "cat /boot/loader/entries/boot.conf"
1526            status, output = qemu.run_serial(cmd)
1527            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1528
1529    def test_fs_types(self):
1530        """Test filesystem types for empty and not empty partitions"""
1531        img = 'core-image-minimal'
1532        with NamedTemporaryFile("w", suffix=".wks") as wks:
1533            wks.writelines(['part ext2   --fstype ext2     --source rootfs\n',
1534                            'part btrfs  --fstype btrfs    --source rootfs --size 40M\n',
1535                            'part squash --fstype squashfs --source rootfs\n',
1536                            'part swap   --fstype swap --size 1M\n',
1537                            'part emptyvfat   --fstype vfat   --size 1M\n',
1538                            'part emptymsdos  --fstype msdos  --size 1M\n',
1539                            'part emptyext2   --fstype ext2   --size 1M\n',
1540                            'part emptybtrfs  --fstype btrfs  --size 150M\n'])
1541            wks.flush()
1542            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1543            runCmd(cmd)
1544            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1545            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1546            self.assertEqual(1, len(out))
1547
1548    def test_kickstart_parser(self):
1549        """Test wks parser options"""
1550        with NamedTemporaryFile("w", suffix=".wks") as wks:
1551            wks.writelines(['part / --fstype ext3 --source rootfs --system-id 0xFF '\
1552                            '--overhead-factor 1.2 --size 100k\n'])
1553            wks.flush()
1554            cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
1555            runCmd(cmd)
1556            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1557            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1558            self.assertEqual(1, len(out))
1559
1560    def test_image_bootpart_globbed(self):
1561        """Test globbed sources with image-bootpart plugin"""
1562        img = "core-image-minimal"
1563        cmd = "wic create sdimage-bootpart -e %s -o %s" % (img, self.resultdir)
1564        config = 'IMAGE_BOOT_FILES = "%s*"' % get_bb_var('KERNEL_IMAGETYPE', img)
1565        self.append_config(config)
1566        runCmd(cmd)
1567        self.remove_config(config)
1568        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "sdimage-bootpart-*direct"))))
1569
1570    def test_sparse_copy(self):
1571        """Test sparse_copy with FIEMAP and SEEK_HOLE filemap APIs"""
1572        libpath = os.path.join(self.td['COREBASE'], 'scripts', 'lib', 'wic')
1573        sys.path.insert(0, libpath)
1574        from  filemap import FilemapFiemap, FilemapSeek, sparse_copy, ErrorNotSupp
1575        with NamedTemporaryFile("w", suffix=".wic-sparse") as sparse:
1576            src_name = sparse.name
1577            src_size = 1024 * 10
1578            sparse.truncate(src_size)
1579            # write one byte to the file
1580            with open(src_name, 'r+b') as sfile:
1581                sfile.seek(1024 * 4)
1582                sfile.write(b'\x00')
1583            dest = sparse.name + '.out'
1584            # copy src file to dest using different filemap APIs
1585            for api in (FilemapFiemap, FilemapSeek, None):
1586                if os.path.exists(dest):
1587                    os.unlink(dest)
1588                try:
1589                    sparse_copy(sparse.name, dest, api=api)
1590                except ErrorNotSupp:
1591                    continue # skip unsupported API
1592                dest_stat = os.stat(dest)
1593                self.assertEqual(dest_stat.st_size, src_size)
1594                # 8 blocks is 4K (physical sector size)
1595                self.assertEqual(dest_stat.st_blocks, 8)
1596            os.unlink(dest)
1597
1598    def test_mkfs_extraopts(self):
1599        """Test wks option --mkfs-extraopts for empty and not empty partitions"""
1600        img = 'core-image-minimal'
1601        with NamedTemporaryFile("w", suffix=".wks") as wks:
1602            wks.writelines(
1603                ['part ext2   --fstype ext2     --source rootfs --mkfs-extraopts "-D -F -i 8192"\n',
1604                 "part btrfs  --fstype btrfs    --source rootfs --size 40M --mkfs-extraopts='--quiet'\n",
1605                 'part squash --fstype squashfs --source rootfs --mkfs-extraopts "-no-sparse -b 4096"\n',
1606                 'part emptyvfat   --fstype vfat   --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1607                 'part emptymsdos  --fstype msdos  --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1608                 'part emptyext2   --fstype ext2   --size 1M --mkfs-extraopts "-D -F -i 8192"\n',
1609                 'part emptybtrfs  --fstype btrfs  --size 100M --mkfs-extraopts "--mixed -K"\n'])
1610            wks.flush()
1611            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1612            runCmd(cmd)
1613            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1614            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1615            self.assertEqual(1, len(out))
1616
1617    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1618    @OETestTag("runqemu")
1619    def test_expand_mbr_image(self):
1620        """Test wic write --expand command for mbr image"""
1621        # build an image
1622        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "directdisk.wks"\n'
1623        self.append_config(config)
1624        image = 'core-image-minimal'
1625        bitbake(image)
1626
1627        # get path to the image
1628        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1629        image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.wic' % bb_vars['IMAGE_LINK_NAME'])
1630
1631        self.remove_config(config)
1632
1633        try:
1634            # expand image to 1G
1635            new_image_path = None
1636            with NamedTemporaryFile(mode='wb', suffix='.wic.exp',
1637                                    dir=bb_vars['DEPLOY_DIR_IMAGE'], delete=False) as sparse:
1638                sparse.truncate(1024 ** 3)
1639                new_image_path = sparse.name
1640
1641            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1642            cmd = "wic write -n %s --expand 1:0 %s %s" % (sysroot, image_path, new_image_path)
1643            runCmd(cmd)
1644
1645            # check if partitions are expanded
1646            orig = runCmd("wic ls %s -n %s" % (image_path, sysroot))
1647            exp = runCmd("wic ls %s -n %s" % (new_image_path, sysroot))
1648            orig_sizes = [int(line.split()[3]) for line in orig.output.split('\n')[1:]]
1649            exp_sizes = [int(line.split()[3]) for line in exp.output.split('\n')[1:]]
1650            self.assertEqual(orig_sizes[0], exp_sizes[0]) # first partition is not resized
1651            self.assertTrue(orig_sizes[1] < exp_sizes[1], msg="Parition size wasn't enlarged (%s vs %s)" % (orig_sizes[1], exp_sizes[1]))
1652
1653            # Check if all free space is partitioned
1654            result = runCmd("%s/usr/sbin/sfdisk -F %s" % (sysroot, new_image_path))
1655            self.assertIn("0 B, 0 bytes, 0 sectors", result.output)
1656
1657            os.rename(image_path, image_path + '.bak')
1658            os.rename(new_image_path, image_path)
1659
1660            runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1661            with runqemu('core-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1662                cmd = "ls /etc/"
1663                status, output = qemu.run_serial('true')
1664                self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1665        finally:
1666            if os.path.exists(new_image_path):
1667                os.unlink(new_image_path)
1668            if os.path.exists(image_path + '.bak'):
1669                os.rename(image_path + '.bak', image_path)
1670
1671    def test_gpt_partition_name(self):
1672        """Test --part-name argument to set partition name in GPT table"""
1673        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "test_gpt_partition_name.wks"\n'
1674        self.append_config(config)
1675        image = 'core-image-minimal'
1676        bitbake(image)
1677        self.remove_config(config)
1678        deploy_dir = get_bb_var('DEPLOY_DIR_IMAGE')
1679        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1680        image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.wic' % bb_vars['IMAGE_LINK_NAME'])
1681
1682        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1683
1684        # Image is created
1685        self.assertTrue(os.path.exists(image_path), "image file %s doesn't exist" % image_path)
1686
1687        # Check the names of the three partitions
1688        # as listed in test_gpt_partition_name.wks
1689        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 1" % (sysroot, image_path))
1690        self.assertEqual('boot-A', result.output)
1691        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 2" % (sysroot, image_path))
1692        self.assertEqual('root-A', result.output)
1693        # When the --part-name is not defined, the partition name is equal to the --label
1694        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 3" % (sysroot, image_path))
1695        self.assertEqual('ext-space', result.output)
1696
1697    def test_empty_zeroize_plugin(self):
1698        img = 'core-image-minimal'
1699        expected_size = [ 1024*1024,    # 1M
1700                          512*1024,     # 512K
1701                          2*1024*1024]  # 2M
1702        # Check combination of sourceparams
1703        with NamedTemporaryFile("w", suffix=".wks") as wks:
1704            wks.writelines(
1705                ['part empty --source empty --sourceparams="fill" --ondisk sda --fixed-size 1M\n',
1706                 'part empty --source empty --sourceparams="size=512K" --ondisk sda --size 1M --align 1024\n',
1707                 'part empty --source empty --sourceparams="size=2048k,bs=512K" --ondisk sda --size 4M --align 1024\n'
1708                 ])
1709            wks.flush()
1710            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1711            runCmd(cmd)
1712            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1713            wicout = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1714            # Skip the complete image and just look at the single partitions
1715            for idx, value in enumerate(wicout[1:]):
1716                self.logger.info(wicout[idx])
1717                # Check if partitions are actually zeroized
1718                with open(wicout[idx], mode="rb") as fd:
1719                    ba = bytearray(fd.read())
1720                    for b in ba:
1721                        self.assertEqual(b, 0)
1722                self.assertEqual(expected_size[idx], os.path.getsize(wicout[idx]))
1723
1724        # Check inconsistancy check between "fill" and "--size" parameter
1725        with NamedTemporaryFile("w", suffix=".wks") as wks:
1726            wks.writelines(['part empty --source empty --sourceparams="fill" --ondisk sda --size 1M\n'])
1727            wks.flush()
1728            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1729            result = runCmd(cmd, ignore_status=True)
1730            self.assertIn("Source parameter 'fill' only works with the '--fixed-size' option, exiting.", result.output)
1731            self.assertNotEqual(0, result.status)
1732
1733class ModifyTests(WicTestCase):
1734    def test_wic_ls(self):
1735        """Test listing image content using 'wic ls'"""
1736        runCmd("wic create wictestdisk "
1737                                   "--image-name=core-image-minimal "
1738                                   "-D -o %s" % self.resultdir)
1739        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1740        self.assertEqual(1, len(images))
1741
1742        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1743
1744        # list partitions
1745        result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
1746        self.assertEqual(3, len(result.output.split('\n')))
1747
1748        # list directory content of the first partition
1749        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1750        self.assertEqual(6, len(result.output.split('\n')))
1751
1752    def test_wic_cp(self):
1753        """Test copy files and directories to the the wic image."""
1754        runCmd("wic create wictestdisk "
1755                                   "--image-name=core-image-minimal "
1756                                   "-D -o %s" % self.resultdir)
1757        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1758        self.assertEqual(1, len(images))
1759
1760        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1761
1762        # list directory content of the first partition
1763        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1764        self.assertEqual(6, len(result.output.split('\n')))
1765
1766        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1767            testfile.write("test")
1768
1769            # copy file to the partition
1770            runCmd("wic cp %s %s:1/ -n %s" % (testfile.name, images[0], sysroot))
1771
1772            # check if file is there
1773            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1774            self.assertEqual(7, len(result.output.split('\n')))
1775            self.assertIn(os.path.basename(testfile.name), result.output)
1776
1777            # prepare directory
1778            testdir = os.path.join(self.resultdir, 'wic-test-cp-dir')
1779            testsubdir = os.path.join(testdir, 'subdir')
1780            os.makedirs(os.path.join(testsubdir))
1781            copy(testfile.name, testdir)
1782
1783            # copy directory to the partition
1784            runCmd("wic cp %s %s:1/ -n %s" % (testdir, images[0], sysroot))
1785
1786            # check if directory is there
1787            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1788            self.assertEqual(8, len(result.output.split('\n')))
1789            self.assertIn(os.path.basename(testdir), result.output)
1790
1791            # copy the file from the partition and check if it success
1792            dest = '%s-cp' % testfile.name
1793            runCmd("wic cp %s:1/%s %s -n %s" % (images[0],
1794                    os.path.basename(testfile.name), dest, sysroot))
1795            self.assertTrue(os.path.exists(dest), msg="File %s wasn't generated as expected" % dest)
1796
1797
1798    def test_wic_rm(self):
1799        """Test removing files and directories from the the wic image."""
1800        runCmd("wic create mkefidisk "
1801                                   "--image-name=core-image-minimal "
1802                                   "-D -o %s" % self.resultdir)
1803        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
1804        self.assertEqual(1, len(images))
1805
1806        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1807        # Not bulletproof but hopefully sufficient
1808        kerneltype = get_bb_var('KERNEL_IMAGETYPE', 'virtual/kernel')
1809
1810        # list directory content of the first partition
1811        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1812        self.assertIn('\n%s ' % kerneltype.upper(), result.output)
1813        self.assertIn('\nEFI          <DIR>     ', result.output)
1814
1815        # remove file. EFI partitions are case-insensitive so exercise that too
1816        runCmd("wic rm %s:1/%s -n %s" % (images[0], kerneltype.lower(), sysroot))
1817
1818        # remove directory
1819        runCmd("wic rm %s:1/efi -n %s" % (images[0], sysroot))
1820
1821        # check if they're removed
1822        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1823        self.assertNotIn('\n%s        ' % kerneltype.upper(), result.output)
1824        self.assertNotIn('\nEFI          <DIR>     ', result.output)
1825
1826    def test_wic_ls_ext(self):
1827        """Test listing content of the ext partition using 'wic ls'"""
1828        runCmd("wic create wictestdisk "
1829                                   "--image-name=core-image-minimal "
1830                                   "-D -o %s" % self.resultdir)
1831        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1832        self.assertEqual(1, len(images))
1833
1834        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1835
1836        # list directory content of the second ext4 partition
1837        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1838        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(
1839                            set(line.split()[-1] for line in result.output.split('\n') if line)), msg="Expected directories not present %s" % result.output)
1840
1841    def test_wic_cp_ext(self):
1842        """Test copy files and directories to the ext partition."""
1843        runCmd("wic create wictestdisk "
1844                                   "--image-name=core-image-minimal "
1845                                   "-D -o %s" % self.resultdir)
1846        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1847        self.assertEqual(1, len(images))
1848
1849        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1850
1851        # list directory content of the ext4 partition
1852        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1853        dirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1854        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(dirs), msg="Expected directories not present %s" % dirs)
1855
1856        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1857            testfile.write("test")
1858
1859            # copy file to the partition
1860            runCmd("wic cp %s %s:2/ -n %s" % (testfile.name, images[0], sysroot))
1861
1862            # check if file is there
1863            result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1864            newdirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1865            self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)]))
1866
1867            # check if the file to copy is in the partition
1868            result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1869            self.assertIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1870
1871            # copy file from the partition, replace the temporary file content with it and
1872            # check for the file size to validate the copy
1873            runCmd("wic cp %s:2/etc/fstab %s -n %s" % (images[0], testfile.name, sysroot))
1874            self.assertTrue(os.stat(testfile.name).st_size > 0, msg="Filesize not as expected %s" % os.stat(testfile.name).st_size)
1875
1876
1877    def test_wic_rm_ext(self):
1878        """Test removing files from the ext partition."""
1879        runCmd("wic create mkefidisk "
1880                                   "--image-name=core-image-minimal "
1881                                   "-D -o %s" % self.resultdir)
1882        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
1883        self.assertEqual(1, len(images))
1884
1885        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1886
1887        # list directory content of the /etc directory on ext4 partition
1888        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1889        self.assertIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1890
1891        # remove file
1892        runCmd("wic rm %s:2/etc/fstab -n %s" % (images[0], sysroot))
1893
1894        # check if it's removed
1895        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1896        self.assertNotIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1897
1898        # remove non-empty directory
1899        runCmd("wic rm -r %s:2/etc/ -n %s" % (images[0], sysroot))
1900
1901        # check if it's removed
1902        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1903        self.assertNotIn('etc', [line.split()[-1] for line in result.output.split('\n') if line])
1904