Files
Matteo Benedetto 090005f6b9 cellar-cli-c: client CLI di Cellar reimplementato in C (statico, retrocompatibile)
Reimplementazione fedele di cellar-cli.py (enne2/cellar @ f5216b1) in C99
POSIX.1-2008, mono-thread, senza dipendenze esterne: miniz e' vendored per
deflate/inflate raw, mentre contenitore gzip, multipart/form-data in streaming,
tar PAX/ustar, JSON e client HTTP/1.1 sono scritti a mano.

- 7 comandi (list, upload, download, install, scan-local, wizard-upload,
  wizard-install) con stdout/stderr/exit code identici al client Python
- 30/30 test di parita' (tests/parity_test.sh) contro due server mock
  indipendenti: output a confronto, alberi installati, interop tar con
  tarfile di Python e con GNU tar, archivio C equivalente a quello Python
- build statiche x86-64 / i686 / aarch64: nessun simbolo GLIBC richiesto,
  nessuna syscall moderna (statx/openat2/memfd_create/getrandom), LFS 64 bit,
  resolver DNS di riserva (hosts + query UDP) per glibc statica senza NSS
- miglioramento rispetto all'originale: upload multipart in streaming invece
  di leggere l'intero archivio in RAM
- divergenze volute documentate nel README (help piu' sintetico, mtime dei
  symlink non ripristinato come tarfile, EOF nei prompt, niente TLS)

Build: make | make 32 | make arm64 | make test | make verify
2026-09-20 17:29:14 +02:00

75 lines
2.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Confronta due alberi di directory: tipo, permessi, dimensione, target dei
symlink (esatti) e mtime (con tolleranza), piu' il contenuto dei file regolari.
Uso: compare_trees.py <dirA> <dirB> [--mtimes] [--tolerance 1e-5]
"""
from __future__ import annotations
import argparse
import hashlib
import os
import stat
import sys
def walk(root: str):
out = {}
for dirpath, dirnames, filenames in os.walk(root):
dirnames.sort()
for name in sorted(filenames) + sorted(dirnames):
full = os.path.join(dirpath, name)
rel = os.path.relpath(full, root)
st = os.lstat(full)
kind = "d" if stat.S_ISDIR(st.st_mode) else ("l" if stat.S_ISLNK(st.st_mode) else "f")
digest = "-"
if kind == "f":
with open(full, "rb") as fh:
digest = hashlib.sha256(fh.read()).hexdigest()[:16]
out[rel] = {
"kind": kind,
"mode": stat.S_IMODE(st.st_mode),
"size": st.st_size,
"mtime": st.st_mtime,
"link": os.readlink(full) if kind == "l" else "-",
"sha": digest,
}
return out
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("a")
ap.add_argument("b")
ap.add_argument("--mtimes", action="store_true", help="confronta anche i mtime")
ap.add_argument("--tolerance", type=float, default=1e-5)
args = ap.parse_args()
a, b = walk(args.a), walk(args.b)
ok = True
for key in sorted(set(a) | set(b)):
if key not in a:
print(f"SOLO IN B: {key}")
ok = False
continue
if key not in b:
print(f"SOLO IN A: {key}")
ok = False
continue
x, y = a[key], b[key]
for field in ("kind", "mode", "size", "link", "sha"):
if x[field] != y[field]:
print(f"DIFF {key}: {field} A={x[field]} B={y[field]}")
ok = False
if args.mtimes and x["kind"] != "l" and abs(x["mtime"] - y["mtime"]) > args.tolerance:
print(f"DIFF {key}: mtime A={x['mtime']:.7f} B={y['mtime']:.7f}")
ok = False
print("alberi identici" if ok else "alberi DIVERSI")
print("(mtime dei symlink non confrontati: tarfile di Python non li applica,")
print(" quindi valgono sempre l'istante dell'estrazione)")
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())