b9874d0941
Add redbear-usb-storage-check in-guest binary that validates USB mass storage read and write I/O: discovers /scheme/disk/ devices, writes a test pattern to sector 2048, reads it back, verifies match, restores original content. Updates test-usb-storage-qemu.sh with write-proof verification step. Includes all accumulated Red Bear OS work: kernel patches, relibc patches, driver infrastructure, DRM/GPU, KDE recipes, firmware, validation tooling, build system hardening, and documentation.
144 lines
4.8 KiB
Python
Executable File
144 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Validate that all source trees required by a build config exist."""
|
|
|
|
import argparse
|
|
import sys
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
META_PACKAGES = {"libgcc", "libstdcxx"}
|
|
|
|
|
|
def build_lookup():
|
|
lookup = {}
|
|
for root in (PROJECT_ROOT / "recipes", PROJECT_ROOT / "local/recipes"):
|
|
for recipe_toml in root.rglob("recipe.toml"):
|
|
parts = recipe_toml.parts
|
|
if "source" in parts or "target" in parts:
|
|
continue
|
|
package_name = recipe_toml.parent.name
|
|
if package_name not in lookup:
|
|
lookup[package_name] = recipe_toml.parent
|
|
return lookup
|
|
|
|
|
|
def resolve_config(config_path: Path, visited=None):
|
|
if visited is None:
|
|
visited = set()
|
|
config_path = config_path.resolve()
|
|
if config_path in visited:
|
|
return {}
|
|
visited.add(config_path)
|
|
with open(config_path, "rb") as config_file:
|
|
config = tomllib.load(config_file)
|
|
packages = dict(config.get("packages", {}))
|
|
for include in config.get("include", []):
|
|
include_path = config_path.parent / include
|
|
if include_path.exists():
|
|
included_packages = resolve_config(include_path, visited)
|
|
for package_name, package_value in packages.items():
|
|
included_packages[package_name] = package_value
|
|
packages = included_packages
|
|
return packages
|
|
|
|
|
|
def recipe_restore_path(recipe_dir: Path):
|
|
recipes_root = PROJECT_ROOT / "recipes"
|
|
try:
|
|
return recipe_dir.relative_to(recipes_root).as_posix()
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def same_as_source_dir(recipe_dir: Path):
|
|
recipe_file = recipe_dir / "recipe.toml"
|
|
if not recipe_file.exists():
|
|
return None
|
|
with open(recipe_file, "rb") as handle:
|
|
recipe = tomllib.load(handle)
|
|
source = recipe.get("source")
|
|
if not isinstance(source, dict):
|
|
return None
|
|
same_as = source.get("same_as")
|
|
if not isinstance(same_as, str) or not same_as:
|
|
return None
|
|
return (recipe_dir / same_as).resolve() / "source"
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("config", nargs="?", default="redbear-full")
|
|
parser.add_argument("--extra-package", action="append", default=[])
|
|
parser.add_argument("--missing-paths-only", action="store_true")
|
|
parser.add_argument("--release", default="")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
config_path = PROJECT_ROOT / "config" / f"{args.config}.toml"
|
|
if not config_path.exists():
|
|
print(f"Config not found: {config_path}", file=sys.stderr)
|
|
return 1
|
|
|
|
lookup = build_lookup()
|
|
packages = resolve_config(config_path)
|
|
requested_packages = dict(packages)
|
|
for package_name in args.extra_package:
|
|
requested_packages.setdefault(package_name, {})
|
|
|
|
missing_recipe_paths = []
|
|
missing_entries = []
|
|
present = 0
|
|
|
|
for package_name, package_conf in sorted(requested_packages.items()):
|
|
if str(package_conf) == "ignore" or package_name in META_PACKAGES:
|
|
continue
|
|
recipe_dir = lookup.get(package_name)
|
|
if recipe_dir is None:
|
|
missing_entries.append((package_name, None))
|
|
continue
|
|
source_dir = recipe_dir / "source"
|
|
if source_dir.is_dir() and any(source_dir.iterdir()):
|
|
present += 1
|
|
continue
|
|
alias_source_dir = same_as_source_dir(recipe_dir)
|
|
if alias_source_dir is not None and alias_source_dir.is_dir() and any(alias_source_dir.iterdir()):
|
|
present += 1
|
|
continue
|
|
missing_entries.append((package_name, recipe_dir))
|
|
restore_path = recipe_restore_path(recipe_dir)
|
|
if restore_path is not None:
|
|
missing_recipe_paths.append(restore_path)
|
|
|
|
if args.missing_paths_only:
|
|
seen = set()
|
|
for recipe_path in missing_recipe_paths:
|
|
if recipe_path not in seen:
|
|
print(recipe_path)
|
|
seen.add(recipe_path)
|
|
return 1 if missing_entries else 0
|
|
|
|
print(f"=== Validating source trees for config: {args.config} ===")
|
|
for package_name, recipe_dir in missing_entries:
|
|
if recipe_dir is None:
|
|
print(f" NOT FOUND: {package_name}")
|
|
else:
|
|
print(f" MISSING: {recipe_dir.relative_to(PROJECT_ROOT)}")
|
|
|
|
total = present + len(missing_entries)
|
|
print(f"\n Total (config): {total}")
|
|
print(f" Present: {present}")
|
|
print(f" Missing: {len(missing_entries)}")
|
|
if missing_entries:
|
|
release = args.release or "<release>"
|
|
print(f"\nTo restore: ./local/scripts/restore-sources.sh --release={release}")
|
|
return 1
|
|
print("All source trees present.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|