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
75 lines
2.5 KiB
Python
Executable File
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())
|