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"""
450                          % (rootfs_dir, rootfs_dir))
451            runCmd("wic create %s -e core-image-minimal -o %s" \
452                                       % (wks_file, self.resultdir))
453
454            os.remove(wks_file)
455            wicout = glob(os.path.join(self.resultdir, "%s-*direct" % 'temp'))
456            self.assertEqual(1, len(wicout))
457
458            wicimg = wicout[0]
459
460            # verify partition size with wic
461            res = runCmd("parted -m %s unit b p" % wicimg, stderr=subprocess.PIPE)
462
463            # parse parted output which looks like this:
464            # BYT;\n
465            # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
466            # 1:0.00MiB:200MiB:200MiB:ext4::;\n
467            partlns = res.output.splitlines()[2:]
468
469            self.assertEqual(3, len(partlns))
470
471            for part in [1, 2, 3]:
472                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
473                partln = partlns[part-1].split(":")
474                self.assertEqual(7, len(partln))
475                start = int(partln[1].rstrip("B")) / 512
476                length = int(partln[3].rstrip("B")) / 512
477                runCmd("dd if=%s of=%s skip=%d count=%d" %
478                                           (wicimg, part_file, start, length))
479
480            # Test partition 1, should contain the normal root directories, except
481            # /usr.
482            res = runCmd("debugfs -R 'ls -p' %s" % \
483                             os.path.join(self.resultdir, "selftest_img.part1"), stderr=subprocess.PIPE)
484            files = extract_files(res.output)
485            self.assertIn("etc", files)
486            self.assertNotIn("usr", files)
487
488            # Partition 2, should contain common directories for /usr, not root
489            # directories.
490            res = runCmd("debugfs -R 'ls -p' %s" % \
491                             os.path.join(self.resultdir, "selftest_img.part2"), stderr=subprocess.PIPE)
492            files = extract_files(res.output)
493            self.assertNotIn("etc", files)
494            self.assertNotIn("usr", files)
495            self.assertIn("share", files)
496
497            # Partition 3, should contain the same as partition 2, including the bin
498            # directory, but not the files inside it.
499            res = runCmd("debugfs -R 'ls -p' %s" % \
500                             os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
501            files = extract_files(res.output)
502            self.assertNotIn("etc", files)
503            self.assertNotIn("usr", files)
504            self.assertIn("share", files)
505            self.assertIn("bin", files)
506            res = runCmd("debugfs -R 'ls -p bin' %s" % \
507                             os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
508            files = extract_files(res.output)
509            self.assertIn(".", files)
510            self.assertIn("..", files)
511            self.assertEqual(2, len(files))
512
513            for part in [1, 2, 3]:
514                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
515                os.remove(part_file)
516
517        finally:
518            os.environ['PATH'] = oldpath
519
520    def test_include_path(self):
521        """Test --include-path wks option."""
522
523        oldpath = os.environ['PATH']
524        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
525
526        try:
527            include_path = os.path.join(self.resultdir, 'test-include')
528            os.makedirs(include_path)
529            with open(os.path.join(include_path, 'test-file'), 'w') as t:
530                t.write("test\n")
531            wks_file = os.path.join(include_path, 'temp.wks')
532            with open(wks_file, 'w') as wks:
533                rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal')
534                wks.write("""
535part /part1 --source rootfs --ondisk mmcblk0 --fstype=ext4
536part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s"""
537                          % (include_path))
538            runCmd("wic create %s -e core-image-minimal -o %s" \
539                                       % (wks_file, self.resultdir))
540
541            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
542            part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0]
543
544            # Test partition 1, should not contain 'test-file'
545            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
546            files = extract_files(res.output)
547            self.assertNotIn('test-file', files)
548            self.assertEqual(True, files_own_by_root(res.output))
549
550            # Test partition 2, should contain 'test-file'
551            res = runCmd("debugfs -R 'ls -p' %s" % (part2), stderr=subprocess.PIPE)
552            files = extract_files(res.output)
553            self.assertIn('test-file', files)
554            self.assertEqual(True, files_own_by_root(res.output))
555
556        finally:
557            os.environ['PATH'] = oldpath
558
559    def test_include_path_embeded(self):
560        """Test --include-path wks option."""
561
562        oldpath = os.environ['PATH']
563        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
564
565        try:
566            include_path = os.path.join(self.resultdir, 'test-include')
567            os.makedirs(include_path)
568            with open(os.path.join(include_path, 'test-file'), 'w') as t:
569                t.write("test\n")
570            wks_file = os.path.join(include_path, 'temp.wks')
571            with open(wks_file, 'w') as wks:
572                wks.write("""
573part / --source rootfs  --fstype=ext4 --include-path %s --include-path core-image-minimal-mtdutils export/"""
574                          % (include_path))
575            runCmd("wic create %s -e core-image-minimal -o %s" \
576                                       % (wks_file, self.resultdir))
577
578            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
579
580            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
581            files = extract_files(res.output)
582            self.assertIn('test-file', files)
583            self.assertEqual(True, files_own_by_root(res.output))
584
585            res = runCmd("debugfs -R 'ls -p /export/etc/' %s" % (part1), stderr=subprocess.PIPE)
586            files = extract_files(res.output)
587            self.assertIn('passwd', files)
588            self.assertEqual(True, files_own_by_root(res.output))
589
590        finally:
591            os.environ['PATH'] = oldpath
592
593    def test_include_path_errors(self):
594        """Test --include-path wks option error handling."""
595        wks_file = 'temp.wks'
596
597        # Absolute argument.
598        with open(wks_file, 'w') as wks:
599            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils /export")
600        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
601                                      % (wks_file, self.resultdir), ignore_status=True).status)
602        os.remove(wks_file)
603
604        # Argument pointing to parent directory.
605        with open(wks_file, 'w') as wks:
606            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils ././..")
607        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
608                                      % (wks_file, self.resultdir), ignore_status=True).status)
609        os.remove(wks_file)
610
611        # 3 Argument pointing to parent directory.
612        with open(wks_file, 'w') as wks:
613            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils export/ dummy")
614        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
615                                      % (wks_file, self.resultdir), ignore_status=True).status)
616        os.remove(wks_file)
617
618    def test_exclude_path_errors(self):
619        """Test --exclude-path wks option error handling."""
620        wks_file = 'temp.wks'
621
622        # Absolute argument.
623        with open(wks_file, 'w') as wks:
624            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path /usr")
625        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
626                                      % (wks_file, self.resultdir), ignore_status=True).status)
627        os.remove(wks_file)
628
629        # Argument pointing to parent directory.
630        with open(wks_file, 'w') as wks:
631            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path ././..")
632        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
633                                      % (wks_file, self.resultdir), ignore_status=True).status)
634        os.remove(wks_file)
635
636    def test_permissions(self):
637        """Test permissions are respected"""
638
639        # prepare wicenv and rootfs
640        bitbake('core-image-minimal core-image-minimal-mtdutils -c do_rootfs_wicenv')
641
642        oldpath = os.environ['PATH']
643        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
644
645        t_normal = """
646part / --source rootfs --fstype=ext4
647"""
648        t_exclude = """
649part / --source rootfs --fstype=ext4 --exclude-path=home
650"""
651        t_multi = """
652part / --source rootfs --ondisk sda --fstype=ext4
653part /export --source rootfs --rootfs=core-image-minimal-mtdutils --fstype=ext4
654"""
655        t_change = """
656part / --source rootfs --ondisk sda --fstype=ext4 --exclude-path=etc/   
657part /etc --source rootfs --fstype=ext4 --change-directory=etc
658"""
659        tests = [t_normal, t_exclude, t_multi, t_change]
660
661        try:
662            for test in tests:
663                include_path = os.path.join(self.resultdir, 'test-include')
664                os.makedirs(include_path)
665                wks_file = os.path.join(include_path, 'temp.wks')
666                with open(wks_file, 'w') as wks:
667                    wks.write(test)
668                runCmd("wic create %s -e core-image-minimal -o %s" \
669                                       % (wks_file, self.resultdir))
670
671                for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')):
672                    res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
673                    self.assertEqual(True, files_own_by_root(res.output))
674
675                config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file
676                self.append_config(config)
677                bitbake('core-image-minimal')
678                tmpdir = os.path.join(get_bb_var('WORKDIR', 'core-image-minimal'),'build-wic')
679
680                # check each partition for permission
681                for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')):
682                    res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
683                    self.assertTrue(files_own_by_root(res.output)
684                        ,msg='Files permission incorrect using wks set "%s"' % test)
685
686                # clean config and result directory for next cases
687                self.remove_config(config)
688                rmtree(self.resultdir, ignore_errors=True)
689
690        finally:
691            os.environ['PATH'] = oldpath
692
693    def test_change_directory(self):
694        """Test --change-directory wks option."""
695
696        oldpath = os.environ['PATH']
697        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
698
699        try:
700            include_path = os.path.join(self.resultdir, 'test-include')
701            os.makedirs(include_path)
702            wks_file = os.path.join(include_path, 'temp.wks')
703            with open(wks_file, 'w') as wks:
704                wks.write("part /etc --source rootfs --fstype=ext4 --change-directory=etc")
705            runCmd("wic create %s -e core-image-minimal -o %s" \
706                                       % (wks_file, self.resultdir))
707
708            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
709
710            res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
711            files = extract_files(res.output)
712            self.assertIn('passwd', files)
713
714        finally:
715            os.environ['PATH'] = oldpath
716
717    def test_change_directory_errors(self):
718        """Test --change-directory wks option error handling."""
719        wks_file = 'temp.wks'
720
721        # Absolute argument.
722        with open(wks_file, 'w') as wks:
723            wks.write("part / --source rootfs --fstype=ext4 --change-directory /usr")
724        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
725                                      % (wks_file, self.resultdir), ignore_status=True).status)
726        os.remove(wks_file)
727
728        # Argument pointing to parent directory.
729        with open(wks_file, 'w') as wks:
730            wks.write("part / --source rootfs --fstype=ext4 --change-directory ././..")
731        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
732                                      % (wks_file, self.resultdir), ignore_status=True).status)
733        os.remove(wks_file)
734
735    def test_no_fstab_update(self):
736        """Test --no-fstab-update wks option."""
737
738        oldpath = os.environ['PATH']
739        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
740
741        # Get stock fstab from base-files recipe
742        bitbake('base-files -c do_install')
743        bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab')
744        self.assertEqual(True, os.path.exists(bf_fstab))
745        bf_fstab_md5sum = runCmd('md5sum %s ' % bf_fstab).output.split(" ")[0]
746
747        try:
748            no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update')
749            os.makedirs(no_fstab_update_path)
750            wks_file = os.path.join(no_fstab_update_path, 'temp.wks')
751            with open(wks_file, 'w') as wks:
752                wks.writelines(['part / --source rootfs --fstype=ext4 --label rootfs\n',
753                                'part /mnt/p2 --source rootfs --rootfs-dir=core-image-minimal ',
754                                '--fstype=ext4 --label p2 --no-fstab-update\n'])
755            runCmd("wic create %s -e core-image-minimal -o %s" \
756                                       % (wks_file, self.resultdir))
757
758            part_fstab_md5sum = []
759            for i in range(1, 3):
760                part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0]
761                part_fstab = runCmd("debugfs -R 'cat etc/fstab' %s" % (part), stderr=subprocess.PIPE)
762                part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest())
763
764            # '/etc/fstab' in partition 2 should contain the same stock fstab file
765            # as the one installed by the base-file recipe.
766            self.assertEqual(bf_fstab_md5sum, part_fstab_md5sum[1])
767
768            # '/etc/fstab' in partition 1 should contain an updated fstab file.
769            self.assertNotEqual(bf_fstab_md5sum, part_fstab_md5sum[0])
770
771        finally:
772            os.environ['PATH'] = oldpath
773
774    def test_no_fstab_update_errors(self):
775        """Test --no-fstab-update wks option error handling."""
776        wks_file = 'temp.wks'
777
778        # Absolute argument.
779        with open(wks_file, 'w') as wks:
780            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update /etc")
781        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
782                                      % (wks_file, self.resultdir), ignore_status=True).status)
783        os.remove(wks_file)
784
785        # Argument pointing to parent directory.
786        with open(wks_file, 'w') as wks:
787            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update ././..")
788        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
789                                      % (wks_file, self.resultdir), ignore_status=True).status)
790        os.remove(wks_file)
791
792    def test_extra_space(self):
793        """Test --extra-space wks option."""
794        extraspace = 1024**3
795        runCmd("wic create wictestdisk "
796                                   "--image-name core-image-minimal "
797                                   "--extra-space %i -o %s" % (extraspace ,self.resultdir))
798        wicout = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
799        self.assertEqual(1, len(wicout))
800        size = os.path.getsize(wicout[0])
801        self.assertTrue(size > extraspace, msg="Extra space not present (%s vs %s)" % (size, extraspace))
802
803    def test_no_table(self):
804        """Test --no-table wks option."""
805        wks_file = 'temp.wks'
806
807        # Absolute argument.
808        with open(wks_file, 'w') as wks:
809            wks.write("part testspace --no-table --fixed-size 16k --offset 4080k")
810        runCmd("wic create %s --image-name core-image-minimal -o %s" % (wks_file, self.resultdir))
811
812        wicout = glob(os.path.join(self.resultdir, "*.*"))
813
814        self.assertEqual(1, len(wicout))
815        size = os.path.getsize(wicout[0])
816        self.assertEqual(size, 4 * 1024 * 1024)
817
818        os.remove(wks_file)
819
820    def test_partition_hidden_attributes(self):
821        """Test --hidden wks option."""
822        wks_file = 'temp.wks'
823        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
824        try:
825            with open(wks_file, 'w') as wks:
826                wks.write("""
827part / --source rootfs --fstype=ext4
828part / --source rootfs --fstype=ext4 --hidden
829bootloader --ptable gpt""")
830
831            runCmd("wic create %s -e core-image-minimal -o %s" \
832                                       % (wks_file, self.resultdir))
833            wicout = os.path.join(self.resultdir, "*.direct")
834
835            result = runCmd("%s/usr/sbin/sfdisk --part-attrs %s 1" % (sysroot, wicout))
836            self.assertEqual('', result.output)
837            result = runCmd("%s/usr/sbin/sfdisk --part-attrs %s 2" % (sysroot, wicout))
838            self.assertEqual('RequiredPartition', result.output)
839
840        finally:
841            os.remove(wks_file)
842
843    def test_wic_sector_size(self):
844        """Test generation image sector size"""
845
846        oldpath = os.environ['PATH']
847        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
848
849        try:
850            # Add WIC_SECTOR_SIZE into config
851            config = 'WIC_SECTOR_SIZE = "4096"\n'\
852                     'WICVARS:append = " WIC_SECTOR_SIZE"\n'
853            self.append_config(config)
854            bitbake('core-image-minimal')
855
856            # Check WIC_SECTOR_SIZE apply to bitbake variable
857            wic_sector_size_str = get_bb_var('WIC_SECTOR_SIZE', 'core-image-minimal')
858            wic_sector_size = int(wic_sector_size_str)
859            self.assertEqual(4096, wic_sector_size)
860
861            self.logger.info("Test wic_sector_size: %d \n" % wic_sector_size)
862
863            with NamedTemporaryFile("w", suffix=".wks") as wks:
864                wks.writelines(
865                    ['bootloader --ptable gpt\n',
866                     'part --fstype ext4 --source rootfs --label rofs-a --mkfs-extraopts "-b 4096"\n',
867                     'part --fstype ext4 --source rootfs --use-uuid --mkfs-extraopts "-b 4096"\n'])
868                wks.flush()
869                cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
870                runCmd(cmd)
871                wksname = os.path.splitext(os.path.basename(wks.name))[0]
872                images = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
873                self.assertEqual(1, len(images))
874
875            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
876            # list partitions
877            result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
878            self.assertEqual(3, len(result.output.split('\n')))
879
880            # verify partition size with wic
881            res = runCmd("export PARTED_SECTOR_SIZE=%d; parted -m %s unit b p" % (wic_sector_size, images[0]),
882                         stderr=subprocess.PIPE)
883
884            # parse parted output which looks like this:
885            # BYT;\n
886            # /var/tmp/wic/build/tmpgjzzefdd-202410281021-sda.direct:78569472B:file:4096:4096:gpt::;\n
887            # 1:139264B:39284735B:39145472B:ext4:rofs-a:;\n
888            # 2:39284736B:78430207B:39145472B:ext4:primary:;\n
889            disk_info = res.output.splitlines()[1]
890            # Check sector sizes
891            sector_size_logical = int(disk_info.split(":")[3])
892            sector_size_physical = int(disk_info.split(":")[4])
893            self.assertEqual(wic_sector_size, sector_size_logical, "Logical sector size is not %d." % wic_sector_size)
894            self.assertEqual(wic_sector_size, sector_size_physical, "Physical sector size is not %d." % wic_sector_size)
895
896        finally:
897            os.environ['PATH'] = oldpath
898
899class Wic2(WicTestCase):
900
901    def test_bmap_short(self):
902        """Test generation of .bmap file -m option"""
903        cmd = "wic create wictestdisk -e core-image-minimal -m -o %s" % self.resultdir
904        runCmd(cmd)
905        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
906        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
907
908    def test_bmap_long(self):
909        """Test generation of .bmap file --bmap option"""
910        cmd = "wic create wictestdisk -e core-image-minimal --bmap -o %s" % self.resultdir
911        runCmd(cmd)
912        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
913        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
914
915    def test_image_env(self):
916        """Test generation of <image>.env files."""
917        image = 'core-image-minimal'
918        imgdatadir = self._get_image_env_path(image)
919
920        bb_vars = get_bb_vars(['IMAGE_BASENAME', 'WICVARS'], image)
921        basename = bb_vars['IMAGE_BASENAME']
922        self.assertEqual(basename, image)
923        path = os.path.join(imgdatadir, basename) + '.env'
924        self.assertTrue(os.path.isfile(path), msg="File %s wasn't generated as expected" % path)
925
926        wicvars = set(bb_vars['WICVARS'].split())
927        # filter out optional variables
928        wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES',
929                                      'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE',
930                                      'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME',
931                                      'APPEND', 'IMAGE_EFI_BOOT_FILES'))
932        with open(path) as envfile:
933            content = dict(line.split("=", 1) for line in envfile)
934            # test if variables used by wic present in the .env file
935            for var in wicvars:
936                self.assertTrue(var in content, "%s is not in .env file" % var)
937                self.assertTrue(content[var], "%s doesn't have a value (%s)" % (var, content[var]))
938
939    def test_image_vars_dir_short(self):
940        """Test image vars directory selection -v option"""
941        image = 'core-image-minimal'
942        imgenvdir = self._get_image_env_path(image)
943        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
944
945        runCmd("wic create wictestdisk "
946                                   "--image-name=%s -v %s -n %s -o %s"
947                                   % (image, imgenvdir, native_sysroot,
948                                      self.resultdir))
949        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
950
951    def test_image_vars_dir_long(self):
952        """Test image vars directory selection --vars option"""
953        image = 'core-image-minimal'
954        imgenvdir = self._get_image_env_path(image)
955        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
956
957        runCmd("wic create wictestdisk "
958                                   "--image-name=%s "
959                                   "--vars %s "
960                                   "--native-sysroot %s "
961                                   "--outdir %s"
962                                   % (image, imgenvdir, native_sysroot,
963                                      self.resultdir))
964        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
965
966    # TODO this test could also work on aarch64
967    @skipIfNotArch(['i586', 'i686', 'x86_64'])
968    def test_wic_image_type(self):
969        """Test building wic images by bitbake"""
970        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
971                 'MACHINE_FEATURES:append = " efi"\n'
972        self.append_config(config)
973        image = 'wic-image-minimal'
974        bitbake(image)
975        self.remove_config(config)
976
977        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
978        prefix = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.' % bb_vars['IMAGE_LINK_NAME'])
979
980        # check if we have result image and manifests symlinks
981        # pointing to existing files
982        for suffix in ('wic', 'manifest'):
983            path = prefix + suffix
984            self.assertTrue(os.path.islink(path), msg="Link %s wasn't generated as expected" % path)
985            self.assertTrue(os.path.isfile(os.path.realpath(path)), msg="File linked to by %s wasn't generated as expected" % path)
986
987    # TODO this should work on aarch64
988    @skipIfNotArch(['i586', 'i686', 'x86_64'])
989    @OETestTag("runqemu")
990    def test_qemu(self):
991        """Test wic-image-minimal under qemu"""
992        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
993                 'MACHINE_FEATURES:append = " efi"\n'
994        self.append_config(config)
995        bitbake('wic-image-minimal')
996        self.remove_config(config)
997
998        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'wic-image-minimal') or ""
999        with runqemu('wic-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1000            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' " \
1001                  "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'"
1002            status, output = qemu.run_serial(cmd)
1003            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1004            self.assertEqual(output, '4')
1005            cmd = "grep UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba /etc/fstab"
1006            status, output = qemu.run_serial(cmd)
1007            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1008            self.assertEqual(output, 'UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba\t/media\text4\tdefaults\t0\t0')
1009
1010    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1011    @OETestTag("runqemu")
1012    def test_qemu_efi(self):
1013        """Test core-image-minimal efi image under qemu"""
1014        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "mkefidisk.wks"\n'
1015        self.append_config(config)
1016        bitbake('core-image-minimal ovmf')
1017        self.remove_config(config)
1018
1019        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1020        with runqemu('core-image-minimal', ssh=False,
1021                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1022            cmd = "grep sda. /proc/partitions  |wc -l"
1023            status, output = qemu.run_serial(cmd)
1024            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1025            self.assertEqual(output, '3')
1026
1027    @staticmethod
1028    def _make_fixed_size_wks(size):
1029        """
1030        Create a wks of an image with a single partition. Size of the partition is set
1031        using --fixed-size flag. Returns a tuple: (path to wks file, wks image name)
1032        """
1033        with NamedTemporaryFile("w", suffix=".wks", delete=False) as tempf:
1034            wkspath = tempf.name
1035            tempf.write("part " \
1036                     "--source rootfs --ondisk hda --align 4 --fixed-size %d "
1037                     "--fstype=ext4\n" % size)
1038
1039        return wkspath
1040
1041    def _get_wic_partitions(self, wkspath, native_sysroot=None, ignore_status=False):
1042        p = runCmd("wic create %s -e core-image-minimal -o %s" % (wkspath, self.resultdir),
1043                   ignore_status=ignore_status)
1044
1045        if p.status:
1046            return (p, None)
1047
1048        wksname = os.path.splitext(os.path.basename(wkspath))[0]
1049
1050        wicout = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1051
1052        if not wicout:
1053            return (p, None)
1054
1055        wicimg = wicout[0]
1056
1057        if not native_sysroot:
1058            native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1059
1060        # verify partition size with wic
1061        res = runCmd("parted -m %s unit kib p" % wicimg,
1062                     native_sysroot=native_sysroot, stderr=subprocess.PIPE)
1063
1064        # parse parted output which looks like this:
1065        # BYT;\n
1066        # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
1067        # 1:0.00MiB:200MiB:200MiB:ext4::;\n
1068        return (p, res.output.splitlines()[2:])
1069
1070    def test_fixed_size(self):
1071        """
1072        Test creation of a simple image with partition size controlled through
1073        --fixed-size flag
1074        """
1075        wkspath = Wic2._make_fixed_size_wks(200)
1076        _, partlns = self._get_wic_partitions(wkspath)
1077        os.remove(wkspath)
1078
1079        self.assertEqual(partlns, [
1080                        "1:4.00kiB:204804kiB:204800kiB:ext4::;",
1081                        ])
1082
1083    def test_fixed_size_error(self):
1084        """
1085        Test creation of a simple image with partition size controlled through
1086        --fixed-size flag. The size of partition is intentionally set to 1MiB
1087        in order to trigger an error in wic.
1088        """
1089        wkspath = Wic2._make_fixed_size_wks(1)
1090        p, _ = self._get_wic_partitions(wkspath, ignore_status=True)
1091        os.remove(wkspath)
1092
1093        self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1094
1095    def test_offset(self):
1096        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1097
1098        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1099            # Test that partitions are placed at the correct offsets, default KB
1100            tempf.write("bootloader --ptable gpt\n" \
1101                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 100M --fstype=ext4\n" \
1102                        "part /bar                 --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n")
1103            tempf.flush()
1104
1105            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1106            self.assertEqual(partlns, [
1107                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
1108                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
1109                ])
1110
1111        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1112            # Test that partitions are placed at the correct offsets, same with explicit KB
1113            tempf.write("bootloader --ptable gpt\n" \
1114                        "part /    --source rootfs --ondisk hda --offset 32K     --fixed-size 100M --fstype=ext4\n" \
1115                        "part /bar                 --ondisk hda --offset 102432K --fixed-size 100M --fstype=ext4\n")
1116            tempf.flush()
1117
1118            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1119            self.assertEqual(partlns, [
1120                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
1121                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
1122                ])
1123
1124        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1125            # Test that partitions are placed at the correct offsets using MB
1126            tempf.write("bootloader --ptable gpt\n" \
1127                        "part /    --source rootfs --ondisk hda --offset 32K  --fixed-size 100M --fstype=ext4\n" \
1128                        "part /bar                 --ondisk hda --offset 101M --fixed-size 100M --fstype=ext4\n")
1129            tempf.flush()
1130
1131            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1132            self.assertEqual(partlns, [
1133                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
1134                "2:103424kiB:205824kiB:102400kiB:ext4:primary:;",
1135                ])
1136
1137        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1138            # Test that partitions can be placed on a 512 byte sector boundary
1139            tempf.write("bootloader --ptable gpt\n" \
1140                        "part /    --source rootfs --ondisk hda --offset 65s --fixed-size 99M --fstype=ext4\n" \
1141                        "part /bar                 --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n")
1142            tempf.flush()
1143
1144            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1145            self.assertEqual(partlns, [
1146                "1:32.5kiB:101408kiB:101376kiB:ext4:primary:;",
1147                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
1148                ])
1149
1150        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1151            # Test that a partition can be placed immediately after a MSDOS partition table
1152            tempf.write("bootloader --ptable msdos\n" \
1153                        "part /    --source rootfs --ondisk hda --offset 1s --fixed-size 100M --fstype=ext4\n")
1154            tempf.flush()
1155
1156            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1157            self.assertEqual(partlns, [
1158                "1:0.50kiB:102400kiB:102400kiB:ext4::;",
1159                ])
1160
1161        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1162            # Test that image creation fails if the partitions would overlap
1163            tempf.write("bootloader --ptable gpt\n" \
1164                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 100M --fstype=ext4\n" \
1165                        "part /bar                 --ondisk hda --offset 102431 --fixed-size 100M --fstype=ext4\n")
1166            tempf.flush()
1167
1168            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1169            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1170
1171        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1172            # Test that partitions are not allowed to overlap with the booloader
1173            tempf.write("bootloader --ptable gpt\n" \
1174                        "part /    --source rootfs --ondisk hda --offset 8 --fixed-size 100M --fstype=ext4\n")
1175            tempf.flush()
1176
1177            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1178            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1179
1180    def test_extra_space(self):
1181        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1182
1183        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1184            tempf.write("bootloader --ptable gpt\n" \
1185                        "part /     --source rootfs --ondisk hda --extra-space 200M --fstype=ext4\n")
1186            tempf.flush()
1187
1188            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1189            self.assertEqual(len(partlns), 1)
1190            size = partlns[0].split(':')[3]
1191            self.assertRegex(size, r'^[0-9]+kiB$')
1192            size = int(size[:-3])
1193            self.assertGreaterEqual(size, 204800)
1194
1195    # TODO this test could also work on aarch64
1196    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1197    @OETestTag("runqemu")
1198    def test_rawcopy_plugin_qemu(self):
1199        """Test rawcopy plugin in qemu"""
1200        # build ext4 and then use it for a wic image
1201        config = 'IMAGE_FSTYPES = "ext4"\n'
1202        self.append_config(config)
1203        bitbake('core-image-minimal')
1204        image_link_name = get_bb_var('IMAGE_LINK_NAME', 'core-image-minimal')
1205        self.remove_config(config)
1206
1207        config = 'IMAGE_FSTYPES = "wic"\n' \
1208                 'IMAGE_LINK_NAME_CORE_IMAGE_MINIMAL = "%s"\n'\
1209                 'WKS_FILE = "test_rawcopy_plugin.wks.in"\n'\
1210                 % image_link_name
1211        self.append_config(config)
1212        bitbake('core-image-minimal-mtdutils')
1213        self.remove_config(config)
1214
1215        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal-mtdutils') or ""
1216        with runqemu('core-image-minimal-mtdutils', ssh=False,
1217                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1218            cmd = "grep sda. /proc/partitions  |wc -l"
1219            status, output = qemu.run_serial(cmd)
1220            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1221            self.assertEqual(output, '2')
1222
1223    def _rawcopy_plugin(self, fstype):
1224        """Test rawcopy plugin"""
1225        image = 'core-image-minimal'
1226        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1227        params = ',unpack' if fstype.endswith('.gz') else ''
1228        with NamedTemporaryFile("w", suffix=".wks") as wks:
1229            wks.write('part / --source rawcopy --sourceparams="file=%s.%s%s"\n'\
1230                      % (bb_vars['IMAGE_LINK_NAME'], fstype, params))
1231            wks.flush()
1232            cmd = "wic create %s -e %s -o %s" % (wks.name, image, self.resultdir)
1233            runCmd(cmd)
1234            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1235            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1236            self.assertEqual(1, len(out))
1237
1238    def test_rawcopy_plugin(self):
1239        config = 'IMAGE_FSTYPES = "ext4"\n'
1240        self.append_config(config)
1241        self.assertEqual(0, bitbake('core-image-minimal').status)
1242        self.remove_config(config)
1243        self._rawcopy_plugin('ext4')
1244
1245    def test_rawcopy_plugin_unpack(self):
1246        fstype = 'ext4.gz'
1247        config = 'IMAGE_FSTYPES = "%s"\n' % fstype
1248        self.append_config(config)
1249        self.assertEqual(0, bitbake('core-image-minimal').status)
1250        self.remove_config(config)
1251        self._rawcopy_plugin(fstype)
1252
1253    def test_empty_plugin(self):
1254        """Test empty plugin"""
1255        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_empty_plugin.wks"\n'
1256        self.append_config(config)
1257        image = 'core-image-minimal'
1258        bitbake(image)
1259        self.remove_config(config)
1260        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1261        image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.wic' % bb_vars['IMAGE_LINK_NAME'])
1262        self.assertTrue(os.path.exists(image_path), msg="Image file %s wasn't generated as expected" % image_path)
1263
1264        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1265
1266        # Fstype column from 'wic ls' should be empty for the second partition
1267        # as listed in test_empty_plugin.wks
1268        result = runCmd("wic ls %s -n %s | awk -F ' ' '{print $1 \" \" $5}' | grep '^2' | wc -w" % (image_path, sysroot))
1269        self.assertEqual('1', result.output)
1270
1271    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1272    @OETestTag("runqemu")
1273    def test_biosplusefi_plugin_qemu(self):
1274        """Test biosplusefi plugin in qemu"""
1275        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1276        self.append_config(config)
1277        bitbake('core-image-minimal')
1278        self.remove_config(config)
1279
1280        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1281        with runqemu('core-image-minimal', ssh=False,
1282                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1283            # Check that we have ONLY two /dev/sda* partitions (/boot and /)
1284            cmd = "grep sda. /proc/partitions | wc -l"
1285            status, output = qemu.run_serial(cmd)
1286            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1287            self.assertEqual(output, '2')
1288            # Check that /dev/sda1 is /boot and that either /dev/root OR /dev/sda2 is /
1289            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' -e '/dev/root /|/dev/sda2 /'"
1290            status, output = qemu.run_serial(cmd)
1291            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1292            self.assertEqual(output, '2')
1293            # Check that /boot has EFI bootx64.efi (required for EFI)
1294            cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l"
1295            status, output = qemu.run_serial(cmd)
1296            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1297            self.assertEqual(output, '1')
1298            # Check that "BOOTABLE" flag is set on boot partition (required for PC-Bios)
1299            # Trailing "cat" seems to be required; otherwise run_serial() sends back echo of the input command
1300            cmd = "fdisk -l /dev/sda | grep /dev/sda1 | awk {print'$2'} | cat"
1301            status, output = qemu.run_serial(cmd)
1302            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1303            self.assertEqual(output, '*')
1304
1305    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1306    def test_biosplusefi_plugin(self):
1307        """Test biosplusefi plugin"""
1308        # Wic generation below may fail depending on the order of the unittests
1309        # This is because bootimg-pcbios (that bootimg-biosplusefi uses) generate its MBR inside STAGING_DATADIR directory
1310        #    which may or may not exists depending on what was built already
1311        # If an image hasn't been built yet, directory ${STAGING_DATADIR}/syslinux won't exists and _get_bootimg_dir()
1312        #   will raise with "Couldn't find correct bootimg_dir"
1313        # The easiest way to work-around this issue is to make sure we already built an image here, hence the bitbake call
1314        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1315        self.append_config(config)
1316        bitbake('core-image-minimal')
1317        self.remove_config(config)
1318
1319        img = 'core-image-minimal'
1320        with NamedTemporaryFile("w", suffix=".wks") as wks:
1321            wks.writelines(['part /boot --active --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\n',
1322                            'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1323                            'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1324            wks.flush()
1325            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1326            runCmd(cmd)
1327            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1328            out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
1329            self.assertEqual(1, len(out))
1330
1331    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
1332    def test_uefi_kernel(self):
1333        """ Test uefi-kernel in wic """
1334        config = 'IMAGE_EFI_BOOT_FILES="/etc/fstab;testfile"\nIMAGE_FSTYPES = "wic"\nWKS_FILE = "test_uefikernel.wks"\nMACHINE_FEATURES:append = " efi"\n'
1335        self.append_config(config)
1336        bitbake('core-image-minimal')
1337        self.remove_config(config)
1338
1339        img = 'core-image-minimal'
1340        with NamedTemporaryFile("w", suffix=".wks") as wks:
1341            wks.writelines(['part /boot --source bootimg-efi --sourceparams="loader=uefi-kernel"\n'
1342                            'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1343                            'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1344            wks.flush()
1345            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1346            runCmd(cmd)
1347            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1348            out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
1349            self.assertEqual(1, len(out))
1350
1351    # TODO this test could also work on aarch64
1352    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1353    @OETestTag("runqemu")
1354    def test_efi_plugin_unified_kernel_image_qemu(self):
1355        """Test Unified Kernel Image feature in qemu without systemd in initramfs or rootfs"""
1356        config = """
1357# efi firmware must load systemd-boot, not grub
1358EFI_PROVIDER = "systemd-boot"
1359
1360# image format must be wic, needs esp partition for firmware etc
1361IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1362WKS_FILE = "test_efi_plugin.wks"
1363
1364# efi, uki and systemd features must be enabled
1365MACHINE_FEATURES:append = " efi"
1366DISTRO_FEATURES_NATIVE:append = " systemd"
1367IMAGE_CLASSES:append:pn-core-image-base = " uki"
1368
1369# uki embeds also an initrd, no systemd or udev
1370INITRAMFS_IMAGE = "core-image-initramfs-boot"
1371
1372# runqemu must not load kernel separately, it's in the uki
1373QB_KERNEL_ROOT = ""
1374QB_DEFAULT_KERNEL = "none"
1375
1376# boot command line provided via uki, not via bootloader
1377UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
1378
1379"""
1380        self.append_config(config)
1381        bitbake('core-image-base ovmf')
1382        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1383        uki_filename = get_bb_var('UKI_FILENAME', 'core-image-base')
1384        self.remove_config(config)
1385
1386        with runqemu('core-image-base', ssh=False,
1387                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1388            # Check that /boot has EFI boot*.efi (required for EFI)
1389            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1390            status, output = qemu.run_serial(cmd)
1391            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1392            self.assertEqual(output, '1')
1393            # Check that /boot has EFI/Linux/${UKI_FILENAME} (required for Unified Kernel Images auto detection)
1394            cmd = "ls /boot/EFI/Linux/%s | wc -l" % (uki_filename)
1395            status, output = qemu.run_serial(cmd)
1396            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1397            self.assertEqual(output, '1')
1398            # Check that /boot doesn't have loader/entries/boot.conf (Unified Kernel Images are auto detected by the bootloader)
1399            cmd = "ls /boot/loader/entries/boot.conf 2&>/dev/null | wc -l"
1400            status, output = qemu.run_serial(cmd)
1401            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1402            self.assertEqual(output, '0')
1403
1404    @skipIfNotArch(['aarch64'])
1405    @OETestTag("runqemu")
1406    def test_efi_plugin_plain_systemd_boot_qemu_aarch64(self):
1407        """Test plain systemd-boot in qemu with systemd"""
1408        config = """
1409INIT_MANAGER = "systemd"
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_plain_systemd-boot.wks"
1415
1416INITRAMFS_IMAGE = "core-image-initramfs-boot"
1417
1418# to configure runqemu
1419IMAGE_CLASSES += "qemuboot"
1420# u-boot efi firmware
1421QB_DEFAULT_BIOS = "u-boot.bin"
1422# need to use virtio, scsi not supported by u-boot by default
1423QB_DRIVE_TYPE = "/dev/vd"
1424
1425# disable kvm, breaks boot
1426QEMU_USE_KVM = ""
1427
1428IMAGE_CLASSES:remove = 'testimage'
1429"""
1430        self.append_config(config)
1431        bitbake('core-image-base u-boot')
1432        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1433
1434        with runqemu('core-image-base', ssh=False,
1435                     runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1436            # Check that /boot has EFI boot*.efi (required for EFI)
1437            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1438            status, output = qemu.run_serial(cmd)
1439            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1440            self.assertEqual(output, '1')
1441            # Check that boot.conf exists
1442            cmd = "cat /boot/loader/entries/boot.conf"
1443            status, output = qemu.run_serial(cmd)
1444            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1445        self.remove_config(config)
1446
1447    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1448    @OETestTag("runqemu")
1449    def test_efi_plugin_plain_systemd_boot_qemu_x86(self):
1450        """Test plain systemd-boot to systemd in qemu"""
1451        config = """
1452INIT_MANAGER = "systemd"
1453EFI_PROVIDER = "systemd-boot"
1454
1455# image format must be wic, needs esp partition for firmware etc
1456IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1457WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1458
1459INITRAMFS_IMAGE = "core-image-initramfs-boot"
1460"""
1461        self.append_config(config)
1462        bitbake('core-image-base ovmf')
1463        runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1464        self.remove_config(config)
1465
1466        with runqemu('core-image-base', ssh=False,
1467                     runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1468            # Check that /boot has EFI boot*.efi (required for EFI)
1469            cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1470            status, output = qemu.run_serial(cmd)
1471            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1472            self.assertEqual(output, '1')
1473            # Check that boot.conf exists
1474            cmd = "cat /boot/loader/entries/boot.conf"
1475            status, output = qemu.run_serial(cmd)
1476            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1477
1478    def test_fs_types(self):
1479        """Test filesystem types for empty and not empty partitions"""
1480        img = 'core-image-minimal'
1481        with NamedTemporaryFile("w", suffix=".wks") as wks:
1482            wks.writelines(['part ext2   --fstype ext2     --source rootfs\n',
1483                            'part btrfs  --fstype btrfs    --source rootfs --size 40M\n',
1484                            'part squash --fstype squashfs --source rootfs\n',
1485                            'part swap   --fstype swap --size 1M\n',
1486                            'part emptyvfat   --fstype vfat   --size 1M\n',
1487                            'part emptymsdos  --fstype msdos  --size 1M\n',
1488                            'part emptyext2   --fstype ext2   --size 1M\n',
1489                            'part emptybtrfs  --fstype btrfs  --size 150M\n'])
1490            wks.flush()
1491            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1492            runCmd(cmd)
1493            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1494            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1495            self.assertEqual(1, len(out))
1496
1497    def test_kickstart_parser(self):
1498        """Test wks parser options"""
1499        with NamedTemporaryFile("w", suffix=".wks") as wks:
1500            wks.writelines(['part / --fstype ext3 --source rootfs --system-id 0xFF '\
1501                            '--overhead-factor 1.2 --size 100k\n'])
1502            wks.flush()
1503            cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
1504            runCmd(cmd)
1505            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1506            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1507            self.assertEqual(1, len(out))
1508
1509    def test_image_bootpart_globbed(self):
1510        """Test globbed sources with image-bootpart plugin"""
1511        img = "core-image-minimal"
1512        cmd = "wic create sdimage-bootpart -e %s -o %s" % (img, self.resultdir)
1513        config = 'IMAGE_BOOT_FILES = "%s*"' % get_bb_var('KERNEL_IMAGETYPE', img)
1514        self.append_config(config)
1515        runCmd(cmd)
1516        self.remove_config(config)
1517        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "sdimage-bootpart-*direct"))))
1518
1519    def test_sparse_copy(self):
1520        """Test sparse_copy with FIEMAP and SEEK_HOLE filemap APIs"""
1521        libpath = os.path.join(self.td['COREBASE'], 'scripts', 'lib', 'wic')
1522        sys.path.insert(0, libpath)
1523        from  filemap import FilemapFiemap, FilemapSeek, sparse_copy, ErrorNotSupp
1524        with NamedTemporaryFile("w", suffix=".wic-sparse") as sparse:
1525            src_name = sparse.name
1526            src_size = 1024 * 10
1527            sparse.truncate(src_size)
1528            # write one byte to the file
1529            with open(src_name, 'r+b') as sfile:
1530                sfile.seek(1024 * 4)
1531                sfile.write(b'\x00')
1532            dest = sparse.name + '.out'
1533            # copy src file to dest using different filemap APIs
1534            for api in (FilemapFiemap, FilemapSeek, None):
1535                if os.path.exists(dest):
1536                    os.unlink(dest)
1537                try:
1538                    sparse_copy(sparse.name, dest, api=api)
1539                except ErrorNotSupp:
1540                    continue # skip unsupported API
1541                dest_stat = os.stat(dest)
1542                self.assertEqual(dest_stat.st_size, src_size)
1543                # 8 blocks is 4K (physical sector size)
1544                self.assertEqual(dest_stat.st_blocks, 8)
1545            os.unlink(dest)
1546
1547    def test_mkfs_extraopts(self):
1548        """Test wks option --mkfs-extraopts for empty and not empty partitions"""
1549        img = 'core-image-minimal'
1550        with NamedTemporaryFile("w", suffix=".wks") as wks:
1551            wks.writelines(
1552                ['part ext2   --fstype ext2     --source rootfs --mkfs-extraopts "-D -F -i 8192"\n',
1553                 "part btrfs  --fstype btrfs    --source rootfs --size 40M --mkfs-extraopts='--quiet'\n",
1554                 'part squash --fstype squashfs --source rootfs --mkfs-extraopts "-no-sparse -b 4096"\n',
1555                 'part emptyvfat   --fstype vfat   --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1556                 'part emptymsdos  --fstype msdos  --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1557                 'part emptyext2   --fstype ext2   --size 1M --mkfs-extraopts "-D -F -i 8192"\n',
1558                 'part emptybtrfs  --fstype btrfs  --size 100M --mkfs-extraopts "--mixed -K"\n'])
1559            wks.flush()
1560            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1561            runCmd(cmd)
1562            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1563            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1564            self.assertEqual(1, len(out))
1565
1566    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1567    @OETestTag("runqemu")
1568    def test_expand_mbr_image(self):
1569        """Test wic write --expand command for mbr image"""
1570        # build an image
1571        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "directdisk.wks"\n'
1572        self.append_config(config)
1573        image = 'core-image-minimal'
1574        bitbake(image)
1575
1576        # get path to the image
1577        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
1578        image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.wic' % bb_vars['IMAGE_LINK_NAME'])
1579
1580        self.remove_config(config)
1581
1582        try:
1583            # expand image to 1G
1584            new_image_path = None
1585            with NamedTemporaryFile(mode='wb', suffix='.wic.exp',
1586                                    dir=bb_vars['DEPLOY_DIR_IMAGE'], delete=False) as sparse:
1587                sparse.truncate(1024 ** 3)
1588                new_image_path = sparse.name
1589
1590            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1591            cmd = "wic write -n %s --expand 1:0 %s %s" % (sysroot, image_path, new_image_path)
1592            runCmd(cmd)
1593
1594            # check if partitions are expanded
1595            orig = runCmd("wic ls %s -n %s" % (image_path, sysroot))
1596            exp = runCmd("wic ls %s -n %s" % (new_image_path, sysroot))
1597            orig_sizes = [int(line.split()[3]) for line in orig.output.split('\n')[1:]]
1598            exp_sizes = [int(line.split()[3]) for line in exp.output.split('\n')[1:]]
1599            self.assertEqual(orig_sizes[0], exp_sizes[0]) # first partition is not resized
1600            self.assertTrue(orig_sizes[1] < exp_sizes[1], msg="Parition size wasn't enlarged (%s vs %s)" % (orig_sizes[1], exp_sizes[1]))
1601
1602            # Check if all free space is partitioned
1603            result = runCmd("%s/usr/sbin/sfdisk -F %s" % (sysroot, new_image_path))
1604            self.assertIn("0 B, 0 bytes, 0 sectors", result.output)
1605
1606            os.rename(image_path, image_path + '.bak')
1607            os.rename(new_image_path, image_path)
1608
1609            runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1610            with runqemu('core-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1611                cmd = "ls /etc/"
1612                status, output = qemu.run_serial('true')
1613                self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1614        finally:
1615            if os.path.exists(new_image_path):
1616                os.unlink(new_image_path)
1617            if os.path.exists(image_path + '.bak'):
1618                os.rename(image_path + '.bak', image_path)
1619
1620    def test_gpt_partition_name(self):
1621        """Test --part-name argument to set partition name in GPT table"""
1622        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "test_gpt_partition_name.wks"\n'
1623        self.append_config(config)
1624        image = 'core-image-minimal'
1625        bitbake(image)
1626        self.remove_config(config)
1627        deploy_dir = get_bb_var('DEPLOY_DIR_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        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1632
1633        # Image is created
1634        self.assertTrue(os.path.exists(image_path), "image file %s doesn't exist" % image_path)
1635
1636        # Check the names of the three partitions
1637        # as listed in test_gpt_partition_name.wks
1638        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 1" % (sysroot, image_path))
1639        self.assertEqual('boot-A', result.output)
1640        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 2" % (sysroot, image_path))
1641        self.assertEqual('root-A', result.output)
1642        # When the --part-name is not defined, the partition name is equal to the --label
1643        result = runCmd("%s/usr/sbin/sfdisk --part-label %s 3" % (sysroot, image_path))
1644        self.assertEqual('ext-space', result.output)
1645
1646    def test_empty_zeroize_plugin(self):
1647        img = 'core-image-minimal'
1648        expected_size = [ 1024*1024,    # 1M
1649                          512*1024,     # 512K
1650                          2*1024*1024]  # 2M
1651        # Check combination of sourceparams
1652        with NamedTemporaryFile("w", suffix=".wks") as wks:
1653            wks.writelines(
1654                ['part empty --source empty --sourceparams="fill" --ondisk sda --fixed-size 1M\n',
1655                 'part empty --source empty --sourceparams="size=512K" --ondisk sda --size 1M --align 1024\n',
1656                 'part empty --source empty --sourceparams="size=2048k,bs=512K" --ondisk sda --size 4M --align 1024\n'
1657                 ])
1658            wks.flush()
1659            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1660            runCmd(cmd)
1661            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1662            wicout = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1663            # Skip the complete image and just look at the single partitions
1664            for idx, value in enumerate(wicout[1:]):
1665                self.logger.info(wicout[idx])
1666                # Check if partitions are actually zeroized
1667                with open(wicout[idx], mode="rb") as fd:
1668                    ba = bytearray(fd.read())
1669                    for b in ba:
1670                        self.assertEqual(b, 0)
1671                self.assertEqual(expected_size[idx], os.path.getsize(wicout[idx]))
1672
1673        # Check inconsistancy check between "fill" and "--size" parameter
1674        with NamedTemporaryFile("w", suffix=".wks") as wks:
1675            wks.writelines(['part empty --source empty --sourceparams="fill" --ondisk sda --size 1M\n'])
1676            wks.flush()
1677            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1678            result = runCmd(cmd, ignore_status=True)
1679            self.assertIn("Source parameter 'fill' only works with the '--fixed-size' option, exiting.", result.output)
1680            self.assertNotEqual(0, result.status)
1681
1682class ModifyTests(WicTestCase):
1683    def test_wic_ls(self):
1684        """Test listing image content using 'wic ls'"""
1685        runCmd("wic create wictestdisk "
1686                                   "--image-name=core-image-minimal "
1687                                   "-D -o %s" % self.resultdir)
1688        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1689        self.assertEqual(1, len(images))
1690
1691        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1692
1693        # list partitions
1694        result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
1695        self.assertEqual(3, len(result.output.split('\n')))
1696
1697        # list directory content of the first partition
1698        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1699        self.assertEqual(6, len(result.output.split('\n')))
1700
1701    def test_wic_cp(self):
1702        """Test copy files and directories to the the wic image."""
1703        runCmd("wic create wictestdisk "
1704                                   "--image-name=core-image-minimal "
1705                                   "-D -o %s" % self.resultdir)
1706        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1707        self.assertEqual(1, len(images))
1708
1709        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1710
1711        # list directory content of the first partition
1712        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1713        self.assertEqual(6, len(result.output.split('\n')))
1714
1715        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1716            testfile.write("test")
1717
1718            # copy file to the partition
1719            runCmd("wic cp %s %s:1/ -n %s" % (testfile.name, images[0], sysroot))
1720
1721            # check if file is there
1722            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1723            self.assertEqual(7, len(result.output.split('\n')))
1724            self.assertIn(os.path.basename(testfile.name), result.output)
1725
1726            # prepare directory
1727            testdir = os.path.join(self.resultdir, 'wic-test-cp-dir')
1728            testsubdir = os.path.join(testdir, 'subdir')
1729            os.makedirs(os.path.join(testsubdir))
1730            copy(testfile.name, testdir)
1731
1732            # copy directory to the partition
1733            runCmd("wic cp %s %s:1/ -n %s" % (testdir, images[0], sysroot))
1734
1735            # check if directory is there
1736            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1737            self.assertEqual(8, len(result.output.split('\n')))
1738            self.assertIn(os.path.basename(testdir), result.output)
1739
1740            # copy the file from the partition and check if it success
1741            dest = '%s-cp' % testfile.name
1742            runCmd("wic cp %s:1/%s %s -n %s" % (images[0],
1743                    os.path.basename(testfile.name), dest, sysroot))
1744            self.assertTrue(os.path.exists(dest), msg="File %s wasn't generated as expected" % dest)
1745
1746
1747    def test_wic_rm(self):
1748        """Test removing files and directories from the the wic image."""
1749        runCmd("wic create mkefidisk "
1750                                   "--image-name=core-image-minimal "
1751                                   "-D -o %s" % self.resultdir)
1752        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
1753        self.assertEqual(1, len(images))
1754
1755        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1756        # Not bulletproof but hopefully sufficient
1757        kerneltype = get_bb_var('KERNEL_IMAGETYPE', 'virtual/kernel')
1758
1759        # list directory content of the first partition
1760        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1761        self.assertIn('\n%s ' % kerneltype.upper(), result.output)
1762        self.assertIn('\nEFI          <DIR>     ', result.output)
1763
1764        # remove file. EFI partitions are case-insensitive so exercise that too
1765        runCmd("wic rm %s:1/%s -n %s" % (images[0], kerneltype.lower(), sysroot))
1766
1767        # remove directory
1768        runCmd("wic rm %s:1/efi -n %s" % (images[0], sysroot))
1769
1770        # check if they're removed
1771        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1772        self.assertNotIn('\n%s        ' % kerneltype.upper(), result.output)
1773        self.assertNotIn('\nEFI          <DIR>     ', result.output)
1774
1775    def test_wic_ls_ext(self):
1776        """Test listing content of the ext partition using 'wic ls'"""
1777        runCmd("wic create wictestdisk "
1778                                   "--image-name=core-image-minimal "
1779                                   "-D -o %s" % self.resultdir)
1780        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1781        self.assertEqual(1, len(images))
1782
1783        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1784
1785        # list directory content of the second ext4 partition
1786        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1787        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(
1788                            set(line.split()[-1] for line in result.output.split('\n') if line)), msg="Expected directories not present %s" % result.output)
1789
1790    def test_wic_cp_ext(self):
1791        """Test copy files and directories to the ext partition."""
1792        runCmd("wic create wictestdisk "
1793                                   "--image-name=core-image-minimal "
1794                                   "-D -o %s" % self.resultdir)
1795        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1796        self.assertEqual(1, len(images))
1797
1798        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1799
1800        # list directory content of the ext4 partition
1801        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1802        dirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1803        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(dirs), msg="Expected directories not present %s" % dirs)
1804
1805        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1806            testfile.write("test")
1807
1808            # copy file to the partition
1809            runCmd("wic cp %s %s:2/ -n %s" % (testfile.name, images[0], sysroot))
1810
1811            # check if file is there
1812            result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1813            newdirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1814            self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)]))
1815
1816            # check if the file to copy is in the partition
1817            result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1818            self.assertIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1819
1820            # copy file from the partition, replace the temporary file content with it and
1821            # check for the file size to validate the copy
1822            runCmd("wic cp %s:2/etc/fstab %s -n %s" % (images[0], testfile.name, sysroot))
1823            self.assertTrue(os.stat(testfile.name).st_size > 0, msg="Filesize not as expected %s" % os.stat(testfile.name).st_size)
1824
1825
1826    def test_wic_rm_ext(self):
1827        """Test removing files from the ext partition."""
1828        runCmd("wic create mkefidisk "
1829                                   "--image-name=core-image-minimal "
1830                                   "-D -o %s" % self.resultdir)
1831        images = glob(os.path.join(self.resultdir, "mkefidisk-*.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 /etc directory on ext4 partition
1837        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1838        self.assertIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1839
1840        # remove file
1841        runCmd("wic rm %s:2/etc/fstab -n %s" % (images[0], sysroot))
1842
1843        # check if it's removed
1844        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1845        self.assertNotIn('fstab', [line.split()[-1] for line in result.output.split('\n') if line])
1846
1847        # remove non-empty directory
1848        runCmd("wic rm -r %s:2/etc/ -n %s" % (images[0], sysroot))
1849
1850        # check if it's removed
1851        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1852        self.assertNotIn('etc', [line.split()[-1] for line in result.output.split('\n') if line])
1853