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