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
cellar-cli-c
Reimplementazione in C del client CLI di Cellar
(cellar-cli.py), con l'obiettivo di un binario il più retrocompatibile possibile:
statico, senza dipendenze, compilabile anche per i686 e aarch64 (handheld).
Autore: Matteo Benedetto — progetto derivato da enne2/cellar (commit f5216b1).
Stato
| Comandi | list, upload, download, install, scan-local, wizard-upload, wizard-install |
| Codice | ~5.800 righe C99 (POSIX.1-2008), mono-thread |
| Dipendenze | nessuna: miniz vendored (deflate/inflate), resto scritto a mano |
| Parità col client Python | 30/30 test automatici (tests/parity_test.sh), output byte-identico |
| Build | x86_64 static, i686 static, aarch64 static (cross) |
| Installazione locale | ~/.local/bin/cellar-cli → symlink a dist/cellar-cli |
Build
make # dist/cellar-cli (x86-64 statico, glibc)
make asan # dist/cellar-cli-asan (dinamico con ASan/UBSan, per i test)
make 32 # dist/cellar-cli-i686 (statico 32 bit)
make arm64 # dist/cellar-cli-aarch64 (cross toolchain ARM GNU 13.2, statico)
make dist # tutti e tre
make verify # analisi di portabilità del binario (vd. sotto)
make test # test di parità contro il client Python (server mock inclusi)
make clean
Variabili utili: CC=..., AARCH64_CC=... (default ~/toolchains/arm-gnu-toolchain-13.2.Rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc).
Installazione nel PATH (utente, nessun root):
ln -sfn "$PWD/dist/cellar-cli" ~/.local/bin/cellar-cli
Configurazione
Stesso file del client Python: ~/.cellar.conf
[cellar]
server = http://brain.vpn:8080
bottles_dir = /home/enne2/.var/app/com.usebottles.bottles/data/bottles/bottles
Se il file non esiste viene creato con i default (e l'avviso su stderr), esattamente
come fa configparser nel client Python. --server URL ha la precedenza.
Comandi
cellar-cli list # elenca gli archivi sul server
cellar-cli upload Backup.tar.gz --name 'Gioco' --bottle-name 'Gioco' \
--description '...' --tags 'gog,rpg' --arch win32 --runner soda-9.0-1 \
--windows-version win10 # upload multipart (streaming)
cellar-cli download 3 ~/Downloads/ # nome file dal Content-Disposition
cellar-cli install 'Gioco' [--replace] # scarica, estrae, installa nella dir Bottles
cellar-cli scan-local [--json] # bottiglie locali (legge bottle.yml)
cellar-cli wizard-upload | wizard-install # flussi interattivi
cellar-cli --server http://10.8.0.3:8080 list # override del server
Exit code: 0 ok, 1 errore HTTP/rete/file, 2 argomenti non validi (come argparse).
Retrocompatibilità (le scelte che contano)
- Binario statico (
-static): nessuna dipendenza da glibc a runtime, quindi gira su distro vecchie e firmware con glibc più vecchia di quella di build.make verifyriporta la nota ABI (for GNU/Linux 3.2.0) e l'assenza di simboliGLIBC_*. - Solo syscall classiche:
open/read/write/stat/lstat/rename/mkdir/symlink/link/utimensat/chmod/poll. Nessunstatx,openat2,O_TMPFILE,memfd_create,getrandom,close_range(verificato automaticamente datests/verify_binary.sh). - Large File Support:
-D_FILE_OFFSET_BITS=64(le bottiglie Cellar arrivano a 1,3 GB). - Resolver di riserva: se
getaddrinfofallisce (tipico di una glibc statica senza NSS a runtime), si passa a/etc/hostse poi a una query DNS diretta su UDP leggendo/etc/resolv.conf. Nessuna dipendenza danss_dns/nss_files. - 32 bit e aarch64:
-m32per sistemi i686 (i vecchi handheld/console), cross aarch64 per muOS/RG40XXH; il binario è statico anche lì, quindi non eredita il problema "toolchain Debian 13 (glibc 2.41) vs firmware (glibc 2.38)". - gzip autonomo: miniz viene usato solo per deflate/inflate raw, mentre header e
trailer gzip (CRC32 + ISIZE) sono gestiti a mano: la API
mz_deflate/mz_inflatedi miniz non supporta il contenitore gzip, e così non serve alcuna libreria zlib (nemmeno per il cross build aarch64).
Parità col client Python
tests/parity_test.sh avvia due server mock indipendenti (stesso stato iniziale) e
confronta per ogni caso stdout, stderr ed exit code del binario C e di
tests/ref/cellar-cli.py (copia verbatim del client originale). Copre: list, upload,
download (su file e su directory), install (prima volta / esistente / --replace /
ref inesistente), 404, server irraggiungibile, entrambi i wizard con input da pipe,
sei casi di errore di argomenti, più verifiche sugli artefatti:
- il download produce byte identici all'upload;
- l'albero installato (tipo, permessi, dimensione, target dei symlink, contenuto, mtime) è identico tra i due client;
- l'archivio creato dal writer C è equivalente a quello creato da
tarfile(confronto membro per membro) ed è leggibile da Python e da GNU tar; - Python riesce a estrarre l'archivio scritto in C con metadata identici.
Differenze note e volute
| Aspetto | Comportamento |
|---|---|
--help / usage |
Struttura e messaggi di errore di argparse riprodotti; il testo descrittivo del --help è più sintetico |
Tar: mtime PAX |
Scritto con 7 decimali (Python usa repr() del float): differenza massima ~1e-7 s |
| Tar: mtime dei symlink in estrazione | Non ripristinato, come tarfile (if not tarinfo.issym()): resta l'istante di estrazione. tests/compare_trees.py lo tiene presente |
| Tar: entry speciali (device) | Saltate con warning su stderr invece di interrompere l'estrazione |
| Confronto nomi case-insensitive | ASCII (tolower) invece di str.lower() Unicode |
| EOF su una richiesta interattiva | Python solleva EOFError (exit 1); qui si stampa EOFError: EOF when reading a line e si esce 1. Nei prompt con default (nome archivio, descrizione…) su EOF si applica il default, così i wizard restano usabili da script |
| Upload multipart | In streaming (il client Python legge l'intero archivio in RAM) |
| TLS | Non supportato (niente OpenSSL): serve http://, oppure un reverse proxy TLS. Il server Cellar è HTTP |
| Timeout di rete | Connect 15 s, I/O 300 s (urllib non ha timeout) |
| Redirect | Seguiti come urllib.request: GET/HEAD su 301/302/303/307/308, POST→GET su 301/302/303 |
Test e verifica
make test # parità (usa la build ASan)
C_BIN=dist/cellar-cli tests/parity_test.sh # parità con il binario statico
make verify # file/ldd/ABI/simboli GLIBC/syscall/smoke test
Struttura
src/common.{c,h} errori, dynbuf, memoria, UTF-8, path, normalizzazione sicura
src/json.{c,h} parser JSON minimale + escaper ensure_ascii
src/http.{c,h} HTTP/1.1 su socket: URL, redirect, chunked, resolver di riserva
src/multipart.{c,h} multipart/form-data in streaming
src/gzip.{c,h} flusso gzip (RFC1952) su deflate raw di miniz
src/tar.{c,h} tar writer PAX/ustar (come tarfile) + extractor con controllo traversal
src/fsutil.{c,h} mkdir -p, rm -r, copia ricorsiva, move con fallback EXDEV, temp dir
src/config.{c,h} ~/.cellar.conf (INI in stile configparser)
src/bottle.{c,h} record archivi, scansione bottiglie, bottle.yml, backup
src/ui.{c,h} tabelle e JSON con larghezze in code point (come le f-string)
src/ops.{c,h} list/upload/download/install
src/wizard.{c,h} flussi interattivi
src/main.c CLI e messaggi di errore in stile argparse
third_party/ miniz (unlicense/MIT) + licenza
tests/ mock server, bottiglia finta, parità, confronti, verifica binario
Licenza e crediti
Codice del progetto Cellar di Matteo Benedetto (enne2/cellar), di cui questa è una
reimplementazione in C. third_party/miniz è distribuito con licenza unlicense/MIT
(vd. third_party/miniz.LICENSE).