#!/usr/bin/env python3 """Sonda wykonalnosci naprawy firmware bez tarballa. Dla kazdej receptury *-firmware: - parsuje lokalny WHENCE (ktore pliki trafiaja do pakietu), - wyciaga z OPUBLIKOWANEJ paczki .pag liste plikow + sha256, - porownuje z tym, co daloby sie pobrac pojedynczo z GitLaba (raw). Raport: ile receptur da sie przestawic na pojedyncze pliki bez zmiany zawartosci. """ import collections import hashlib import os import re import subprocess import sys import tempfile R = "/var/lib/pagan-sync/recipes" REPO = "/var/www/repo.paganlinux.eu/stable" REV = "20260622" RAW = "https://gitlab.com/kernel-firmware/linux-firmware/-/raw/%s/" % REV # rodzaje wpisow w WHENCE KIND = re.compile(r"^(File|Source|Link|Raw|Version|Licence|Info|Driver|File:|Source:|Link:)\b", re.I) def wh_find_local(): out = [] for dp, _d, fs in os.walk(R): if ".git" in dp: continue if "PAGBUILD.yaml" in fs: try: import yaml d = yaml.safe_load(open(os.path.join(dp, "PAGBUILD.yaml"), newline="", encoding="utf-8")) or {} except Exception: continue n = str(d.get("pkgname", "")) if n.endswith("-firmware") and "linux-firmware" in str(d.get("source")): w = os.path.join(dp, "WHENCE") out.append((os.path.relpath(dp, R), n, str(d.get("pkgver")), w if os.path.isfile(w) else None)) return sorted(out) def parse_whence(path): """Zwraca liste wpisow: dict(kind, value). Prosty parser: linie 'Klucz: wartosc'.""" entries = [] cur = None for line in open(path, encoding="utf-8", errors="replace"): s = line.rstrip("\n") m = re.match(r"^(File|Source|Link|Raw|Version|Info|Licence|Driver):\s*(.*)$", s) if m: k, v = m.group(1), m.group(2).strip() if k == "File": cur = {"file": v, "source": None, "link": None, "raw": None} entries.append(cur) elif cur is not None and k in ("Source", "Link", "Raw"): cur[k.lower()] = v return entries def pag_files(name, ver): """Zwraca {sciezka: sha256} z opublikowanej paczki (o ile jest).""" f = os.path.join(REPO, f"{name}-{ver}-1.pag") if not os.path.isfile(f): alt = [x for x in os.listdir(REPO) if x.startswith(f"{name}-{ver}-") and x.endswith(".pag")] if not alt: return None f = os.path.join(REPO, sorted(alt)[-1]) tmp = tempfile.mkdtemp(prefix="pag-") r = subprocess.run(["tar", "-xf", f, "-C", tmp], capture_output=True, text=True) dt = os.path.join(tmp, "data.tar.xz") if r.returncode != 0 or not os.path.isfile(dt): return None subprocess.run(["tar", "-xJf", dt, "-C", tmp], capture_output=True) sums = {} for dp, _d, fs in os.walk(tmp): for x in fs: p = os.path.join(dp, x) rel = os.path.relpath(p, tmp) if rel.startswith("metadata.json") or rel == "sums.json": continue h = hashlib.sha256() with open(p, "rb") as fh: for c in iter(lambda: fh.read(1 << 20), b""): h.update(c) sums[rel] = h.hexdigest() return sums def main(): recipes = wh_find_local() print(f"receptur *-firmware korzystajacych z linux-firmware: {len(recipes)}") kinds = collections.Counter() no_whence = 0 for rel, name, ver, w in recipes: if not w: no_whence += 1 continue for e in parse_whence(w): for k in ("file", "source", "link", "raw"): if e.get(k): kinds[k] += 1 print(f"bez pliku WHENCE: {no_whence}") print("wystapienia pol w WHENCE:", dict(kinds)) print() print("przyklady (pierwsze 8):") for rel, name, ver, w in recipes[:8]: es = parse_whence(w) if w else [] print(f" {name} [{ver}] wpisow={len(es)}") for e in es[:3]: print(" ", e) if __name__ == "__main__": sys.exit(main())