#!/usr/bin/env python3 # SPDX-License-Identifier: MPL-2.0 """Check the ARM64 reference in a private mount namespace, without starting services.""" import argparse import hashlib import json import os from pathlib import Path import subprocess import sys from urllib.parse import quote def run(*args, check=True): result = subprocess.run(args, check=False, capture_output=True, text=True, timeout=45) if check and result.returncode: raise RuntimeError(f'{args}: {result.stderr.strip()}') return result def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--root', type=Path, required=True) parser.add_argument('--product', type=Path, required=True) parser.add_argument('--inside-namespace', action='store_true', help=argparse.SUPPRESS) args = parser.parse_args() if os.geteuid() != 0: parser.error('chroot and the private mount namespace require administrator privileges') root, product = args.root.resolve(), args.product.resolve() base = Path('/home/droidian/vulpes-tundra-tests') for path in (root, product): if not path.is_relative_to(base) or path == base: parser.error('Expected isolated device trial directories') if not (root.parent/(root.name+'-stage.json')).is_file(): parser.error('Staging report missing') if not args.inside_namespace: result = subprocess.run(['unshare', '--mount', '--net', '--fork', sys.executable, str(Path(__file__).resolve()), '--inside-namespace', '--root', str(root), '--product', str(product)], check=False) raise SystemExit(result.returncode) if os.readlink('/proc/self/ns/mnt') == os.readlink('/proc/1/ns/mnt'): parser.error('Refusing to mount in the system namespace') run('mount', '--make-rprivate', '/') run('mount', '--bind', str(product), str(root/'opt/vulpes')) run('mount', '-o', 'remount,bind,ro', str(root/'opt/vulpes')) run('mount', '-t', 'proc', 'proc', str(root/'proc')) for name in ('null', 'urandom', 'random'): (root/'dev'/name).touch(exist_ok=True) run('mount', '--bind', '/dev/'+name, str(root/'dev'/name)) for name in ('run', 'tmp'): run('mount', '-t', 'tmpfs', '-o', 'mode=1777,size=128m', 'tmpfs', str(root/name)) os.chown(root/'home/vulpes', 1000, 1000) profile = root/'home/vulpes/render-profile' profile.mkdir(exist_ok=True) os.chown(profile, 1000, 1000) (root/'home/vulpes/render.png').unlink(missing_ok=True) run('chroot', str(root), '/sbin/ldconfig') compiler = '/usr/lib/aarch64-linux-gnu/glib-2.0/glib-compile-schemas' run('chroot', str(root), compiler, '/usr/share/glib-2.0/schemas') engine = json.loads((product/'engine-lock.json').read_text())['version'] binary = f'/opt/vulpes/engines/{engine}/firefox/firefox' commands = { 'python': ['/usr/bin/python3', '-c', 'import platform,ssl,json; print(json.dumps({"machine":platform.machine(),"python":platform.python_version(),"ssl":ssl.OPENSSL_VERSION}))'], 'compositor': ['/usr/bin/phoc', '--version'], 'gecko': [binary, '--headless', '--version'], 'geckoRendering': [binary, '--headless', '--no-remote', '--profile', '/home/vulpes/render-profile', '--window-size', '360,740', '--screenshot', '/home/vulpes/render.png', 'data:text/html,'+quote('