#!/usr/bin/env python3 """Remove/purge the tested local package in the dedicated validation VM.""" import argparse import hashlib import json import os from pathlib import Path import subprocess def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--smoke', type=Path, required=True) parser.add_argument('--output', type=Path, required=True) args = parser.parse_args() assert os.getuid() != 0 and Path.home() == Path('/home/docview') smoke = json.loads((args.smoke / 'report.json').read_text()) assert smoke['success'] and smoke['smokeConditions'] == 12 for name, digest in smoke['executables'].items(): assert sha(Path('/opt/docview/bin') / name) == digest args.output.mkdir(parents=True, exist_ok=False) record = {'success': False, 'runnerSha256': sha(Path(__file__)), 'smokeReportSha256': sha(args.smoke / 'report.json'), 'commands': []} def run(name, command, allowed=(0,)): result = subprocess.run(command, capture_output=True, text=True, timeout=180) log = args.output / (name + '.log') log.write_text(result.stdout + result.stderr) record['commands'].append({'name': name, 'command': command, 'exitCode': result.returncode, 'logSha256': sha(log)}) assert result.returncode in allowed, name return result.stdout def profiles(label): return run(label, ['sudo', 'cat', '/sys/kernel/security/apparmor/profiles']) bundled = 'docview-bundled-qtwebengine ' sentinels = [] try: before = profiles('profiles-installed') assert bundled in before and 'docview-qtwebengine ' in before record['installedPackage'] = run('package-installed', ['dpkg-query', '-W', '-f=${Package} ${Version} ${db:Status-Status} ${Installed-Size}\n', 'docview']).strip() assert ' installed ' in record['installedPackage'] assert run('userns-before', ['sysctl', '-n', 'kernel.apparmor_restrict_unprivileged_userns']).strip() == '1' for relative in ['.config/docview', '.local/state/docview', '.local/share/docview', 'validation/package-user-document']: directory = Path.home() / relative directory.mkdir(parents=True, exist_ok=True) path = directory / 'deb-preservation-probe.txt' with path.open('x') as stream: stream.write('DocView package removal preservation probe — 日本語\n') sentinels.append({'path': str(path), 'sha256': sha(path)}) run('apt-remove', ['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', 'remove', 'docview']) after = profiles('profiles-removed') assert bundled not in after and 'docview-qtwebengine ' in after for name in ['/opt/docview', '/usr/bin/docview', '/usr/share/applications/docview.desktop']: assert not Path(name).exists(), name assert Path('/etc/apparmor.d/docview').is_file() assert all(sha(Path(row['path'])) == row['sha256'] for row in sentinels) record['remove'] = {'payloadRemoved': True, 'profileUnloaded': True, 'conffilePreserved': True, 'userProbesPreserved': True} run('apt-purge', ['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', 'purge', 'docview']) assert not Path('/etc/apparmor.d/docview').exists() assert all(sha(Path(row['path'])) == row['sha256'] for row in sentinels) assert run('userns-after', ['sysctl', '-n', 'kernel.apparmor_restrict_unprivileged_userns']).strip() == '1' assert Path('/opt/docview-qt/6.11.2/gcc_64/bin/qtpaths').is_file() assert Path('/opt/docview-deps/lib/libqpdf.so.30').is_file() assert Path('/home/docview/docview-install/bin/docview').is_file() assert Path('/home/docview/docview-build/docview').is_file() record['purge'] = {'conffileRemoved': True, 'userProbesPreserved': True} record.update(success=True, userProbes=sentinels, originalDevelopmentPrefixesRestored=True, kernelUserNamespaceRestrictionUnchanged=True) finally: # Delete only these newly-created probes after preserving their hashes. for row in sentinels: path = Path(row['path']) if path.is_file() and sha(path) == row['sha256']: path.unlink() (args.output / 'report.json').write_text(json.dumps(record, indent=2, sort_keys=True) + '\n') print(json.dumps({key: record[key] for key in ('success', 'remove', 'purge')})) if __name__ == '__main__': main()