#!/usr/bin/env python3 # # Helpers for preparing pseudo-managed rootfs trees (NFS booting, SDK/rootfs # extraction, target deploy). # # SPDX-License-Identifier: GPL-2.0-only """Extract rootfs tarballs and locate their pseudo state for NFS booting.""" import os import subprocess from pathlib import Path class PseudoRootfsError(Exception): """Raised when a pseudo-managed rootfs cannot be prepared or exported.""" def pseudo_native_environment(): """Return the pseudo native environment (PSEUDO and OECORE_NATIVE_SYSROOT), from the qemu-helper-native recipe which provides a pseudo binary usable outside a recipe sysroot.""" native_sysroot = os.environ.get('OECORE_NATIVE_SYSROOT') if not native_sysroot: try: import bb.tinfoil except ImportError as exc: raise PseudoRootfsError( 'Unable to import bitbake.\n' 'Did you forget to source your build system environment setup script?') from exc try: with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(quiet=2) native_sysroot = tinfoil.parse_recipe('qemu-helper-native').getVar('STAGING_DIR_NATIVE') except Exception as exc: raise PseudoRootfsError('Unable to set up the qemu-helper-native sysroot') from exc if not native_sysroot or not os.path.exists(native_sysroot): raise PseudoRootfsError("%s doesn't exist" % native_sysroot) environment = {'OECORE_NATIVE_SYSROOT': native_sysroot} environment['PSEUDO'] = os.path.join(native_sysroot, 'usr', 'bin', 'pseudo') return environment def _tar_options(rootfs_tarball): tar_extract_options = { '.tar.xz': '-xJf', '.tar.bz2': '-xjf', '.tar.gz': '-xzf', '.tar.zst': '--zstd -xf', '.tar': '-xf', } for extension, option in tar_extract_options.items(): if rootfs_tarball.endswith(extension): return ['--numeric-owner', *option.split()] raise PseudoRootfsError( 'Unable to determine sdk tarball format\n' 'Accepted types: .tar / .tar.gz / .tar.bz2 / .tar.xz / .tar.zst') def pseudo_state_dir(rootfs_dir): """Return the pseudo database location associated with an extracted rootfs.""" return os.path.realpath(rootfs_dir) + '.pseudo_state' def extract_sdk_rootfs(rootfs_tarball, rootfs_dir, pseudo_cmd, environment): """Extract a rootfs tarball under pseudo and return its absolute directory. pseudo_cmd is the pseudo invocation prefix, e.g. a recipe's own [FAKEROOTCMD] or [pseudo, '-P', native_sysroot_usr] from pseudo_native_environment(). environment supplies the matching pseudo database/env vars (e.g. a parsed FAKEROOTENV, or pseudo_native_environment() itself); PSEUDO_LOCALSTATEDIR/PSEUDO_INCLUDE_PATHS are always overridden here to point at rootfs_dir's own pseudo state, regardless of what's already set in environment. """ if not os.path.exists(rootfs_tarball): raise PseudoRootfsError("sdk tarball '%s' does not exist" % rootfs_tarball) rootfs_tarball = os.path.realpath(rootfs_tarball) rootfs_dir = os.path.realpath(rootfs_dir) tar_options = _tar_options(rootfs_tarball) state_dir = pseudo_state_dir(rootfs_dir) debug_image = '-dbg' in os.path.basename(rootfs_tarball) if os.path.exists(state_dir) and not debug_image: raise PseudoRootfsError( '%s already exists!\n' 'Please delete the rootfs tree and pseudo directory manually\n' 'if this is really what you want.' % state_dir) os.makedirs(rootfs_dir, exist_ok=True) os.makedirs(state_dir, exist_ok=True) Path(state_dir, 'pseudo.pid').touch() environment = dict(environment) environment['PSEUDO_LOCALSTATEDIR'] = state_dir environment['PSEUDO_INCLUDE_PATHS'] = rootfs_dir command = list(pseudo_cmd) + ['tar', '-C', rootfs_dir] + tar_options + [rootfs_tarball] print('Extracting rootfs tarball using pseudo...') print(' '.join(command)) try: subprocess.run(command, env=environment, check=True) except subprocess.CalledProcessError as exc: raise PseudoRootfsError('Failed to extract rootfs tarball') from exc if len(os.listdir(rootfs_dir)) < 4: print("Warning: I don't see many files in %s" % rootfs_dir) print('Please double-check the extraction worked as intended') else: print('SDK image successfully extracted to %s' % rootfs_dir) return rootfs_dir