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

46 lines
1.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Stampa una descrizione canonica di un archivio tar (per confronti di parita').
Uso: tar_dump.py <archivio.tar.gz> [--tolerance SECONDI]
Ogni riga: tipo modo uid gid mtime size linkname name sha256(contenuto)
Serve a verificare che l'archivio prodotto dal writer C sia strutturalmente
equivalente a quello prodotto da tarfile di Python.
"""
from __future__ import annotations
import argparse
import hashlib
import sys
import tarfile
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("archive")
ap.add_argument("--tolerance", type=float, default=1e-6)
args = ap.parse_args()
try:
with tarfile.open(args.archive, "r:gz") as tf:
members = tf.getmembers()
for m in sorted(members, key=lambda x: x.name):
digest = "-"
if m.isreg():
fh = tf.extractfile(m)
if fh is not None:
digest = hashlib.sha256(fh.read()).hexdigest()[:16]
link = m.linkname or "-"
print(
f"{m.type.decode() if isinstance(m.type, bytes) else m.type} "
f"{m.mode:04o} {m.uid} {m.gid} {m.mtime:.7f} {m.size} {link} "
f"{m.name} {digest}"
)
except Exception as exc: # noqa: BLE001
print(f"ERROR: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())