1# SPDX-License-Identifier: GPL-2.0
2# Copyright (c) 2017 Alison Chaiken
3# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
4
5# Test GPT manipulation commands.
6
7import os
8import pytest
9import u_boot_utils
10
11"""
12These tests rely on a 4 MB disk image, which is automatically created by
13the test.
14"""
15
16# Mark all tests here as slow
17pytestmark = pytest.mark.slow
18
19def parse_gpt_parts(disk_str):
20    """Parser a partition string into a list of partitions.
21
22    Args:
23        disk_str: The disk description string, as returned by `gpt read`
24
25    Returns:
26        A list of parsed partitions. Each partition is a dictionary with the
27        string value from each specified key in the partition description, or a
28        key with with the value True for a boolean flag
29    """
30    parts = []
31    for part_str in disk_str.split(';'):
32        part = {}
33        for option in part_str.split(","):
34            if not option:
35                continue
36
37            if "=" in option:
38                key, value = option.split("=")
39                part[key] = value
40            else:
41                part[option] = True
42
43        if part:
44            parts.append(part)
45
46    return parts
47
48class GptTestDiskImage(object):
49    """Disk Image used by the GPT tests."""
50
51    def __init__(self, u_boot_console):
52        """Initialize a new GptTestDiskImage object.
53
54        Args:
55            u_boot_console: A U-Boot console.
56
57        Returns:
58            Nothing.
59        """
60
61        filename = 'test_gpt_disk_image.bin'
62
63        persistent = u_boot_console.config.persistent_data_dir + '/' + filename
64        self.path = u_boot_console.config.result_dir  + '/' + filename
65
66        with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
67            if os.path.exists(persistent):
68                u_boot_console.log.action('Disk image file ' + persistent +
69                    ' already exists')
70            else:
71                u_boot_console.log.action('Generating ' + persistent)
72                fd = os.open(persistent, os.O_RDWR | os.O_CREAT)
73                os.ftruncate(fd, 4194304)
74                os.close(fd)
75                cmd = ('sgdisk',
76                    '--disk-guid=375a56f7-d6c9-4e81-b5f0-09d41ca89efe',
77                    persistent)
78                u_boot_utils.run_and_log(u_boot_console, cmd)
79                # part1 offset 1MB size 1MB
80                cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1',
81                    '--partition-guid=1:33194895-67f6-4561-8457-6fdeed4f50a3',
82                    '-A 1:set:2',
83                    persistent)
84                # part2 offset 2MB size 1.5MB
85                u_boot_utils.run_and_log(u_boot_console, cmd)
86                cmd = ('sgdisk', '--new=2:4096:7167', '--change-name=2:part2',
87                    '--partition-guid=2:cc9c6e4a-6551-4cb5-87be-3210f96c86fb',
88                    persistent)
89                u_boot_utils.run_and_log(u_boot_console, cmd)
90                cmd = ('sgdisk', '--load-backup=' + persistent)
91                u_boot_utils.run_and_log(u_boot_console, cmd)
92
93        cmd = ('cp', persistent, self.path)
94        u_boot_utils.run_and_log(u_boot_console, cmd)
95
96@pytest.fixture(scope='function')
97def state_disk_image(u_boot_console):
98    """pytest fixture to provide a GptTestDiskImage object to tests.
99    This is function-scoped because it uses u_boot_console, which is also
100    function-scoped. A new disk is returned each time to prevent tests from
101    interfering with each other."""
102
103    return GptTestDiskImage(u_boot_console)
104
105@pytest.mark.boardspec('sandbox')
106@pytest.mark.buildconfigspec('cmd_gpt')
107@pytest.mark.buildconfigspec('cmd_part')
108@pytest.mark.requiredtool('sgdisk')
109def test_gpt_read(state_disk_image, u_boot_console):
110    """Test the gpt read command."""
111
112    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
113    output = u_boot_console.run_command('gpt read host 0')
114    assert 'Start 1MiB, size 1MiB' in output
115    assert 'Block size 512, name part1' in output
116    assert 'Start 2MiB, size 1MiB' in output
117    assert 'Block size 512, name part2' in output
118    output = u_boot_console.run_command('part list host 0')
119    assert '0x00000800	0x00000fff	"part1"' in output
120    assert '0x00001000	0x00001bff	"part2"' in output
121
122@pytest.mark.boardspec('sandbox')
123@pytest.mark.buildconfigspec('cmd_gpt')
124@pytest.mark.buildconfigspec('partition_type_guid')
125@pytest.mark.requiredtool('sgdisk')
126def test_gpt_read_var(state_disk_image, u_boot_console):
127    """Test the gpt read command."""
128
129    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
130    output = u_boot_console.run_command('gpt read host 0 gpt_parts')
131    assert 'success!' in output
132
133    output = u_boot_console.run_command('echo ${gpt_parts}')
134    parts = parse_gpt_parts(output.rstrip())
135
136    assert parts == [
137        {
138            "uuid_disk": "375a56f7-d6c9-4e81-b5f0-09d41ca89efe",
139        },
140        {
141            "name": "part1",
142            "start": "0x100000",
143            "size": "0x100000",
144            "type": "0fc63daf-8483-4772-8e79-3d69d8477de4",
145            "uuid": "33194895-67f6-4561-8457-6fdeed4f50a3",
146            "bootable": True,
147        },
148        {
149            "name": "part2",
150            "start": "0x200000",
151            "size": "0x180000",
152            "type": "0fc63daf-8483-4772-8e79-3d69d8477de4",
153            "uuid": "cc9c6e4a-6551-4cb5-87be-3210f96c86fb",
154        },
155    ]
156
157@pytest.mark.boardspec('sandbox')
158@pytest.mark.buildconfigspec('cmd_gpt')
159@pytest.mark.requiredtool('sgdisk')
160def test_gpt_verify(state_disk_image, u_boot_console):
161    """Test the gpt verify command."""
162
163    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
164    output = u_boot_console.run_command('gpt verify host 0')
165    assert 'Verify GPT: success!' in output
166
167@pytest.mark.boardspec('sandbox')
168@pytest.mark.buildconfigspec('cmd_gpt')
169@pytest.mark.requiredtool('sgdisk')
170def test_gpt_repair(state_disk_image, u_boot_console):
171    """Test the gpt repair command."""
172
173    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
174    output = u_boot_console.run_command('gpt repair host 0')
175    assert 'Repairing GPT: success!' in output
176
177@pytest.mark.boardspec('sandbox')
178@pytest.mark.buildconfigspec('cmd_gpt')
179@pytest.mark.requiredtool('sgdisk')
180def test_gpt_guid(state_disk_image, u_boot_console):
181    """Test the gpt guid command."""
182
183    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
184    output = u_boot_console.run_command('gpt guid host 0')
185    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
186
187@pytest.mark.boardspec('sandbox')
188@pytest.mark.buildconfigspec('cmd_gpt')
189@pytest.mark.requiredtool('sgdisk')
190def test_gpt_setenv(state_disk_image, u_boot_console):
191    """Test the gpt setenv command."""
192    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
193    output = u_boot_console.run_command('gpt setenv host 0 part1')
194    assert 'success!' in output
195    output = u_boot_console.run_command('echo ${gpt_partition_addr}')
196    assert output.rstrip() == '800'
197    output = u_boot_console.run_command('echo ${gpt_partition_size}')
198    assert output.rstrip() == '800'
199    output = u_boot_console.run_command('echo ${gpt_partition_name}')
200    assert output.rstrip() == 'part1'
201    output = u_boot_console.run_command('echo ${gpt_partition_entry}')
202    assert output.rstrip() == '1'
203    output = u_boot_console.run_command('echo ${gpt_partition_bootable}')
204    assert output.rstrip() == '1'
205
206    output = u_boot_console.run_command('gpt setenv host 0 part2')
207    assert 'success!' in output
208    output = u_boot_console.run_command('echo ${gpt_partition_addr}')
209    assert output.rstrip() == '1000'
210    output = u_boot_console.run_command('echo ${gpt_partition_size}')
211    assert output.rstrip() == 'c00'
212    output = u_boot_console.run_command('echo ${gpt_partition_name}')
213    assert output.rstrip() == 'part2'
214    output = u_boot_console.run_command('echo ${gpt_partition_entry}')
215    assert output.rstrip() == '2'
216    output = u_boot_console.run_command('echo ${gpt_partition_bootable}')
217    assert output.rstrip() == '0'
218
219@pytest.mark.boardspec('sandbox')
220@pytest.mark.buildconfigspec('cmd_gpt')
221@pytest.mark.requiredtool('sgdisk')
222def test_gpt_save_guid(state_disk_image, u_boot_console):
223    """Test the gpt guid command to save GUID into a string."""
224
225    if u_boot_console.config.buildconfig.get('config_cmd_gpt', 'n') != 'y':
226        pytest.skip('gpt command not supported')
227    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
228    output = u_boot_console.run_command('gpt guid host 0 newguid')
229    output = u_boot_console.run_command('printenv newguid')
230    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
231
232@pytest.mark.boardspec('sandbox')
233@pytest.mark.buildconfigspec('cmd_gpt')
234@pytest.mark.requiredtool('sgdisk')
235def test_gpt_part_type_uuid(state_disk_image, u_boot_console):
236    """Test the gpt partittion type UUID command."""
237
238    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
239    output = u_boot_console.run_command('part type host 0:1')
240    assert '0fc63daf-8483-4772-8e79-3d69d8477de4' in output
241
242@pytest.mark.boardspec('sandbox')
243@pytest.mark.buildconfigspec('cmd_gpt')
244@pytest.mark.requiredtool('sgdisk')
245def test_gpt_part_type_save_uuid(state_disk_image, u_boot_console):
246    """Test the gpt partittion type to save UUID into a string."""
247
248    if u_boot_console.config.buildconfig.get('config_cmd_gpt', 'n') != 'y':
249        pytest.skip('gpt command not supported')
250    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
251    output = u_boot_console.run_command('part type host 0:1 newguid')
252    output = u_boot_console.run_command('printenv newguid')
253    assert '0fc63daf-8483-4772-8e79-3d69d8477de4' in output
254
255@pytest.mark.boardspec('sandbox')
256@pytest.mark.buildconfigspec('cmd_gpt')
257@pytest.mark.buildconfigspec('cmd_gpt_rename')
258@pytest.mark.buildconfigspec('cmd_part')
259@pytest.mark.requiredtool('sgdisk')
260def test_gpt_rename_partition(state_disk_image, u_boot_console):
261    """Test the gpt rename command to write partition names."""
262
263    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
264    u_boot_console.run_command('gpt rename host 0 1 first')
265    output = u_boot_console.run_command('gpt read host 0')
266    assert 'name first' in output
267    u_boot_console.run_command('gpt rename host 0 2 second')
268    output = u_boot_console.run_command('gpt read host 0')
269    assert 'name second' in output
270    output = u_boot_console.run_command('part list host 0')
271    assert '0x00000800	0x00000fff	"first"' in output
272    assert '0x00001000	0x00001bff	"second"' in output
273
274@pytest.mark.boardspec('sandbox')
275@pytest.mark.buildconfigspec('cmd_gpt')
276@pytest.mark.buildconfigspec('cmd_gpt_rename')
277@pytest.mark.buildconfigspec('cmd_part')
278@pytest.mark.requiredtool('sgdisk')
279def test_gpt_swap_partitions(state_disk_image, u_boot_console):
280    """Test the gpt swap command to exchange two partition names."""
281
282    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
283    output = u_boot_console.run_command('part list host 0')
284    assert '0x00000800	0x00000fff	"part1"' in output
285    assert '0x00001000	0x00001bff	"part2"' in output
286    u_boot_console.run_command('gpt swap host 0 part1 part2')
287    output = u_boot_console.run_command('part list host 0')
288    assert '0x00000800	0x00000fff	"part2"' in output
289    assert '0x00001000	0x00001bff	"part1"' in output
290
291@pytest.mark.buildconfigspec('cmd_gpt')
292@pytest.mark.buildconfigspec('cmd_gpt_rename')
293@pytest.mark.buildconfigspec('cmd_part')
294@pytest.mark.requiredtool('sgdisk')
295def test_gpt_set_bootable(state_disk_image, u_boot_console):
296    """Test the gpt set-bootable command."""
297
298    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
299    parts = ('part2', 'part1')
300    for bootable in parts:
301        output = u_boot_console.run_command(f'gpt set-bootable host 0 {bootable}')
302        assert 'success!' in output
303
304        for p in parts:
305            output = u_boot_console.run_command(f'gpt setenv host 0 {p}')
306            assert 'success!' in output
307            output = u_boot_console.run_command('echo ${gpt_partition_bootable}')
308            if p == bootable:
309                assert output.rstrip() == '1'
310            else:
311                assert output.rstrip() == '0'
312
313@pytest.mark.boardspec('sandbox')
314@pytest.mark.buildconfigspec('cmd_gpt')
315@pytest.mark.buildconfigspec('cmd_part')
316@pytest.mark.requiredtool('sgdisk')
317def test_gpt_write(state_disk_image, u_boot_console):
318    """Test the gpt write command."""
319
320    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
321    output = u_boot_console.run_command('gpt write host 0 "name=all,size=0"')
322    assert 'Writing GPT: success!' in output
323    output = u_boot_console.run_command('part list host 0')
324    assert '0x00000022	0x00001fde	"all"' in output
325    output = u_boot_console.run_command('gpt write host 0 "uuid_disk=375a56f7-d6c9-4e81-b5f0-09d41ca89efe;name=first,start=1M,size=1M;name=second,start=0x200000,size=0x180000;"')
326    assert 'Writing GPT: success!' in output
327    output = u_boot_console.run_command('part list host 0')
328    assert '0x00000800	0x00000fff	"first"' in output
329    assert '0x00001000	0x00001bff	"second"' in output
330    output = u_boot_console.run_command('gpt guid host 0')
331    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
332
333@pytest.mark.buildconfigspec('cmd_gpt')
334@pytest.mark.buildconfigspec('cmd_gpt_rename')
335@pytest.mark.buildconfigspec('cmd_part')
336@pytest.mark.requiredtool('sgdisk')
337def test_gpt_transpose(state_disk_image, u_boot_console):
338    """Test the gpt transpose command."""
339
340    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
341    output = u_boot_console.run_command('part list host 0')
342    assert '1\t0x00000800\t0x00000fff\t"part1"' in output
343    assert '2\t0x00001000\t0x00001bff\t"part2"' in output
344
345    output = u_boot_console.run_command('gpt transpose host 0 1 2')
346    assert 'success!' in output
347
348    output = u_boot_console.run_command('part list host 0')
349    assert '2\t0x00000800\t0x00000fff\t"part1"' in output
350    assert '1\t0x00001000\t0x00001bff\t"part2"' in output
351