update docs

This commit is contained in:
2026-09-22 13:23:34 +08:00
parent 99bc3d15c5
commit 8a4788fca8
126 changed files with 7198 additions and 2425 deletions
+45 -48
View File
@@ -14,12 +14,19 @@ from image_formats import gpt, LINUX_FILESYSTEM
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--cli', type=Path, required=True)
parser.add_argument('--image-tool-runner', type=Path)
parser.add_argument('--cc', nargs='+', default=['aarch64-linux-gnu-gcc'])
parser.add_argument('--void-packages', type=Path, default=project / 'vendor/void-packages')
parser.add_argument('--xbps-tool-runner', type=Path)
parser.add_argument('--xbps-bin', type=Path)
args = parser.parse_args()
work = Path(tempfile.mkdtemp(prefix='workstation-images.', dir=project / 'out'))
cli = [str(args.cli.resolve())]
if args.image_tool_runner:
cli += ['--image-tool-runner', str(args.image_tool_runner)]
cli += ['--void-packages', str(args.void_packages.resolve())]
if args.xbps_tool_runner:
cli += ['--xbps-tool-runner', str(args.xbps_tool_runner.resolve())]
if args.xbps_bin:
cli += ['--xbps-bin', str(args.xbps_bin.resolve())]
log = (work / 'commands.log').open('w')
@@ -36,39 +43,32 @@ def digest(path):
return hashlib.file_digest(stream, 'sha256').hexdigest()
(work / 'hello-root/bin').mkdir(parents=True)
(work / 'report-root/bin').mkdir(parents=True)
(work / 'hello.c').write_text('#include <stdio.h>\nint main(void) { puts("FDS cartridge AArch64 hello"); return 0; }\n')
script = work / 'report-root/bin/report'
script.write_text('#!/bin/sh\nprintf "FDS cartridge script report\\n"\nid\n[ "${1-}" != hold ] || exec tail -f /dev/null\n')
script.chmod(0o755)
recipe = f'''format=1
id="demo.hello"
name="AArch64 hello"
version="1.0"
architecture="aarch64"
root="hello-root"
[commands]
hello="bin/hello"
[build]
directory={json.dumps(str(project))}
command={json.dumps([*args.cc, '-O2', str(work / 'hello.c'), '-o', str(work / 'hello-root/bin/hello')])}
'''
(work / 'hello.toml').write_text(recipe)
(work / 'report.toml').write_text('format=1\nid="demo.report"\nname="Script report"\nversion="1.0"\narchitecture="any"\nroot="report-root"\n[commands]\nreport="bin/report"\n')
invoke(['software', 'build', work / 'hello.toml', work / 'hello-bundle'])
invoke(['software', 'pack', work / 'report.toml', work / 'report-bundle'])
# Architecture-independent bundles cannot hide actual ELF executables.
(work / 'wrong-architecture.toml').write_text(recipe.replace('architecture="aarch64"', 'architecture="any"'))
invoke(['software', 'pack', work / 'wrong-architecture.toml', work / 'rejected-architecture'], False)
assert not (work / 'rejected-architecture').exists()
# Source links outside the software root must never become bundle contents.
(work / 'report-root/bin/escape').symlink_to('/etc/passwd')
invoke(['software', 'pack', work / 'report.toml', work / 'rejected-symlink'], False)
assert not (work / 'rejected-symlink').exists()
(work / 'report-root/bin/escape').unlink()
invoke(['software', 'pack', work / 'report.toml', work / 'report-bundle'], False)
(work / 'cartridge.toml').write_text('format=1\nid="demo.workstation"\nname="Workstation software"\nversion="1.0"\n[[payload]]\nbundles=["hello-bundle"]\n[[payload]]\nbundles=["report-bundle"]\n')
for name in ['hello', 'report']:
recipe = project / f'examples/software/{name}/software.toml'
if name == 'report':
text = recipe.read_text().replace('[commands]', '[commands]\nshell="usr/bin/bash"\nwhere="usr/bin/printenv"')
text = text.replace('template = "void"', 'template = ' + json.dumps(str(recipe.parent / 'void')))
recipe = work / 'report-source.toml'; recipe.write_text(text)
invoke(['software', 'build', recipe, work / f'{name}-installed'])
inspected = json.loads(invoke(['software', 'inspect', work / f'{name}-installed']))
assert inspected['installed'] and inspected['packages']
assert f'fds-demo-{name}-1.0_1' in inspected['packages']
assert any(p.startswith('glibc-') for p in inspected['packages'])
assert (work / f'{name}-installed/root/usr/bin/{name}').is_file()
assert not list((work / f'{name}-installed').glob('*.tar.xz'))
# The actual installed trees, including command links, are integrity checked.
program = work / 'hello-installed/root/usr/bin/hello'
original_program = program.read_bytes()
bad_elf = bytearray(original_program); bad_elf[18:20] = bytes([62, 0])
program.write_bytes(bad_elf)
invoke(['software', 'inspect', work / 'hello-installed'], False)
program.write_bytes(original_program)
escape = work / 'report-installed/root/usr/bin/escape'
escape.symlink_to('/etc/passwd')
invoke(['software', 'inspect', work / 'report-installed'], False)
escape.unlink()
invoke(['software', 'build', project / 'examples/software/hello/software.toml', work / 'hello-installed'], False)
(work / 'cartridge.toml').write_text('format=2\nid="demo.workstation"\nname="Workstation software"\nversion="1.0"\n[[payload]]\nsources=["hello-installed"]\n[[payload]]\nsources=["report-installed"]\n')
image = work / 'software.img'
original = json.loads(invoke(['create', work / 'cartridge.toml', image]))
assert len(original['image']['partitions']) == 3
@@ -78,18 +78,15 @@ assert [p['name'] for p in observed['partitions']] == ['FDS_METADATA', 'FDS_PAYL
invoke(['create', work / 'cartridge.toml', work / 'repeat.img'])
assert digest(image) == digest(work / 'repeat.img')
invoke(['create', work / 'cartridge.toml', image], False)
shared = (work / 'cartridge.toml').read_text().replace('bundles=["hello-bundle"]\n[[payload]]\nbundles=["report-bundle"]', 'bundles=["hello-bundle","report-bundle"]')
(work / 'collision.toml').write_text((work / 'cartridge.toml').read_text().replace('demo.workstation', 'demo.second'))
invoke(['create', work / 'collision.toml', work / 'collision.img'])
shared = (work / 'cartridge.toml').read_text().replace('sources=["hello-installed"]\n[[payload]]\nsources=["report-installed"]', 'sources=["hello-installed","report-installed"]')
(work / 'shared.toml').write_text(shared)
grouped = json.loads(invoke(['create', work / 'shared.toml', work / 'shared.img']))
assert len(grouped['image']['partitions']) == 2
assert [s['partition'] for s in grouped['catalogue']['software']] == [2, 2]
for name in ['software.img', 'shared.img']:
subprocess.run(['sfdisk', '--verify', str(work / name)], check=True, stdout=log, stderr=log)
for directory in ['hello-bundle', 'report-bundle']:
archive = next((work / directory).glob('*.tar.xz'))
subprocess.run(['xz', '--test', str(archive)], check=True)
subprocess.run(['tar', '-tJf', str(archive)], check=True, stdout=log)
size = image.stat().st_size
for label, extra in [('exact', 0), ('larger', 8 * 1024 * 1024)]:
target = work / (label + '.target')
@@ -135,14 +132,14 @@ for part in parts:
target.write(source.read(part['bytes']))
filesystems.append(filesystem)
tools = [str(args.image_tool_runner.resolve())] if args.image_tool_runner else []
for case in ['archive-corrupt', 'catalogue-mapping']:
number = 2 if case == 'archive-corrupt' else 1
for case in ['program-corrupt', 'catalogue-mapping']:
number = 2 if case == 'program-corrupt' else 1
tree = work / (case + '-tree')
subprocess.run([*tools, 'fsck.erofs', '--extract=' + str(tree), str(filesystems[number-1])], check=True, stdout=log, stderr=log)
if number == 2:
archive = next((tree / 'bundles').glob('*.tar.xz'))
content = bytearray(archive.read_bytes()); content[len(content)//2] ^= 1
archive.write_bytes(content)
program = tree / 'programs/demo.hello/usr/bin/hello'
content = bytearray(program.read_bytes()); content[len(content)//2] ^= 1
program.write_bytes(content)
else:
metadata = tree / 'FDS/SOFTWARE.TOML'
metadata.write_text(metadata.read_text().replace('partition = 2', 'partition = 4'))
@@ -153,11 +150,11 @@ for case in ['archive-corrupt', 'catalogue-mapping']:
gpt(malformed, [(part['name'], LINUX_FILESYSTEM, filesystem) for part, filesystem in zip(parts, selected)])
invoke(['inspect', malformed], False)
record = dict(status='passed', work=str(work), cli_sha256=digest(args.cli.resolve()), compiled_aarch64_software=True,
script_bundle=True, elf_in_any_bundle_and_escaping_source_symlink_rejected=True, shared_and_separate_payload_partitions=True,
repeat_image_identical=True, independent_gpt_xz_tar_checks=True,
void_source_packages=True, installed_runtime_dependencies=True, wrong_elf_and_escaping_symlink_rejected=True, shared_and_separate_payload_partitions=True,
repeat_image_identical=True, direct_installed_erofs_programs=True,
exact_and_larger_target_readback=True, wrong_confirmation_unchanged=True,
changed_source_unchanged_target=True, stale_target_preview_rejected=True,
corrupted_gpt_rejected=True, corrupt_archive_and_catalogue_rejected=True, physical_usb_write='not performed')
corrupted_gpt_rejected=True, corrupt_program_and_catalogue_rejected=True, physical_usb_write='not performed')
(work / 'acceptance.json').write_text(json.dumps(record, indent=2) + '\n')
(project / 'out/workstation-images-current.txt').write_text(str(work) + '\n')
print('PASS: workstation software/image/write acceptance:', work)