Skip to content

Latest commit

 

History

History
284 lines (243 loc) · 13 KB

File metadata and controls

284 lines (243 loc) · 13 KB

libsquish API reference

Everything lives in squish.h; link with -lsquish -lm. All functions are exported with default visibility from libsquish.so (or __declspec on Windows — define SQUISH_DLL when consuming the DLL, nothing when linking the static library).

Model of operation

libsquish has one on-disk format, the SQUISH archive. A buffer or a single file becomes a one-member archive; a directory tree becomes a many-member archive. Inside, a member's bytes are one or more independently-coded blocks (each up to chunk_size), which is what lets big members (de)compress in parallel and lets a reader seek to any member. The full byte layout is in FORMAT.md.

The algorithm's match model addresses the whole of a block, so the ratio is best when a member is one block; splitting into blocks trades roughly 1–2% of ratio (at the 16 MiB default) for parallelism. Memory per active block is ~150 MB of model state plus your buffers, released as blocks finish.

Thread safety: the library has no global mutable state. Any number of threads may compress/decompress concurrently as long as they don't share buffers. Pass nthreads to parallelize within one call; an archive reader handle, by contrast, is not thread-safe — use one per thread or serialize.

Status codes: every function returns int; SQUISH_OK (0) on success, negative squish_status otherwise. squish_strerror renders any code.

code meaning
SQUISH_OK (0) success
SQUISH_E_PARAM (-1) NULL pointer or invalid argument
SQUISH_E_NOMEM (-2) allocation failure
SQUISH_E_FORMAT (-3) bad magic/header, corrupt archive, or truncation
SQUISH_E_DSTSIZE (-4) destination capacity insufficient
SQUISH_E_TOOBIG (-5) a member ≥ SQUISH_MAX_INPUT (4 GiB − 16)
SQUISH_E_IO (-6) file open/read/write failed (file helpers only)
SQUISH_E_CHECKSUM (-7) a block decoded but its integrity check failed

Introspection

const char *squish_version(void);      /* "1.0.0" */
const char *squish_strerror(int code); /* human-readable, static, never NULL */
int         squish_threads(void);      /* processors online; what nthreads=0 picks */

Progress reporting

typedef void (*squish_progress_fn)(uint64_t processed, uint64_t total,
                                   void *user);

progress (wherever accepted; may be NULL) is invoked with the count of original — uncompressed — bytes handled so far, periodically and once with processed == total on completion. It is called from the coding loop, so keep it cheap and do not call back into the library. Under multiple threads calls are serialized by the library but may arrive on worker threads; processed stays monotonic across the whole member.

In-memory: buffer ⇄ one-member archive

size_t squish_compress_bound(size_t src_len);

Worst-case archive size for src_len input bytes — a hard guarantee: a buffer of at least this capacity can always hold the result, because incompressible data falls back to stored blocks.

int squish_compress_alloc(const void *src, size_t src_len,
                          void **dst, size_t *dst_len,
                          int nthreads, size_t chunk_size,
                          squish_progress_fn progress, void *user);

Compresses src[0..src_len) into a freshly allocated archive holding a single unnamed member. nthreads (0 = all cores, 1 = serial) and chunk_size (0 = SQUISH_DEFAULT_CHUNK, 16 MiB; raised to SQUISH_MIN_CHUNK, 64 KiB, if smaller) shape the member's blocks. The bytes produced depend only on chunk_size, never on nthreads. On any error *dst is NULL. Free with squish_free.

int squish_decompress_alloc(const void *src, size_t src_len,
                            void **dst, size_t *dst_len,
                            int nthreads, squish_progress_fn progress, void *user);

Restores the single member of the archive into a freshly allocated buffer, verifying every block's checksum. Rejects an archive that does not hold exactly one file member with SQUISH_E_FORMAT (use the squish_archive_* API for multi-member archives). SQUISH_E_CHECKSUM means a block decoded but the data does not match what was compressed — treat the output as garbage. Corrupt or truncated input never crashes; it returns an error.

int  squish_content_size(const void *src, size_t src_len, uint64_t *out_size);
void squish_free(void *p);

content_size reads the archive's total uncompressed size from the header (needs 32 bytes; no decompression). Release any *_alloc/*_extract buffer with squish_free — not plain free — so the allocator matches across DLL boundaries on Windows.

File helpers

int squish_compress_file(const char *src_path, const char *dst_path,
                         int nthreads, size_t chunk_size,
                         squish_progress_fn progress, void *user);
int squish_decompress_file(const char *src_path, const char *dst_path,
                           int nthreads, squish_progress_fn progress, void *user);

compress_file packs one file into a one-member archive at dst_path (the member takes the source's basename, and the archive's SINGLE flag is set). decompress_file restores a one-member archive to dst_path, rejecting a multi-member archive with SQUISH_E_FORMAT (extract those with the archive API). nthreads/chunk_size/progress behave as above; SQUISH_E_IO covers filesystem trouble.


Archives

A directory tree is packed into a seekable archive: a header, one independently-compressed member per file, and a compressed index of paths and per-file block layout. A reader inflates the small index at open time, then reaches any single member by seeking to its blocks — the rest of the archive is never touched. The container is specified byte-for-byte in FORMAT.md §1.

int squish_archive_create(const char *dir_path, const char *archive_path,
                          int nthreads, size_t chunk_size,
                          squish_progress_fn cb, void *user);

Walks dir_path and writes a seekable archive to archive_path, compressing each file as its own member. nthreads/chunk_size behave as above (a file larger than one chunk is split into parallel blocks); cb reports total uncompressed bytes packed. Directories (including empty ones) and unix mode bits are recorded; sockets, fifos, and dangling links are skipped. Members are stored pre-order, siblings sorted, so the archive depends only on the tree — not on directory iteration order.

int  squish_archive_probe(const void *data, size_t len);   /* needs 12 bytes */
int  squish_archive_open(const char *path, squish_archive **out);
int  squish_archive_open_memory(const void *data, size_t len, squish_archive **out);
void squish_archive_close(squish_archive *a);

open reads and validates only the header and the index — work proportional to the member count, not the archive size — keeping the file open to seek per extraction. open_memory borrows a buffer that must outlive the handle (it must not be freed until close). probe is a cheap sniff for telling an archive from other data. A handle is not thread-safe; use one per thread, or serialize calls.

int      squish_archive_info_get(const squish_archive *a, squish_archive_info *out);
uint64_t squish_archive_count(const squish_archive *a);
int      squish_archive_stat(const squish_archive *a, uint64_t index,
                             squish_archive_entry *out);
int      squish_archive_find(const squish_archive *a, const char *path,
                             uint64_t *index_out);

Header and listing, all served from the in-memory index with no decompression. info exposes version, flags (bit 0 = SINGLE), entry_count, total_size, and chunk_size. stat fills a squish_archive_entry (path, size, stored_size, mode, is_dir); its path points into the handle and is valid until close (it is "" for the unnamed member of a blob archive). find returns SQUISH_E_FORMAT when the path is not a member (a trailing / on the query is ignored).

int squish_archive_extract(squish_archive *a, uint64_t index,
                           void **out, size_t *out_len);
int squish_archive_extract_path(squish_archive *a, const char *path,
                                void **out, size_t *out_len);

Inflate one member into a library-allocated buffer (free with squish_free), reading and decoding only that member's blocks and verifying their checksums. A directory member returns SQUISH_E_PARAM.

int squish_archive_extract_to_file(squish_archive *a, const char *path,
                                   const char *dst_path);
int squish_archive_extract_subtree(squish_archive *a, const char *prefix,
                                   const char *dst_root,
                                   squish_progress_fn cb, void *user);

Extract to the filesystem, inflating only what is needed. _to_file writes a single file member to dst_path (its parent directories must already exist). _extract_subtree recreates, under dst_root, every member whose path is prefix or lies beneath it — each landing at dst_root/<archive path>; prefix NULL or "" extracts the whole archive. Member paths are re-validated against traversal on the way out, so an archive can never write outside dst_root.

Editing an archive in place

#define SQUISH_ADD_KEEP_EXISTING 1u   /* skip files already present */

int squish_archive_add(const char *archive_path, const char *src_path,
                       const char *arc_path, int nthreads, unsigned flags,
                       squish_progress_fn cb, void *user);
int squish_archive_remove(const char *archive_path,
                          const char *const *paths, size_t npaths,
                          squish_progress_fn cb, void *user);

Add or remove members without re-packing the whole archive. Members that survive the edit are copied byte-for-byte — their already-compressed blocks are moved verbatim — so only files being added are ever compressed. The cost is one pass of I/O over the archive plus compressing just the added bytes, not a full re-pack (which, at SQUISH's ~0.5 MB/s, is the difference between seconds and hours on a large archive). Each call rebuilds into a sibling temp file and atomically replaces the original, so an interrupted or failing edit leaves the existing archive untouched. The result is byte-identical to what squish_archive_create would produce from the same resulting tree.

_add merges the file or directory tree at src_path into the archive under the archive-relative arc_path ("docs/readme.txt"), creating any missing parent directories. A directory merges into an existing one of the same name: its files are added, existing files it does not mention stay in place, and a file that collides with an existing file is overwritten — or, with SQUISH_ADD_KEEP_EXISTING in flags, kept (a file/directory type clash at the same path is SQUISH_E_PARAM). Added files are compressed at the archive's existing block size — which is fixed for the whole archive — using nthreads. _remove drops every member whose path is one of paths[0..npaths) or lies beneath it, and returns SQUISH_E_FORMAT (leaving the archive unchanged) if nothing matches; removing every member yields a valid empty archive.

Both require a packed (tree) archive: an archive with the SINGLE flag — a lone file or blob from squish_compress_file/squish_compress_alloc — returns SQUISH_E_PARAM.

/* list every member, then pull one file out — reading only its blocks */
squish_archive *a;
if (squish_archive_open("photos.sqsh", &a) == SQUISH_OK) {
    uint64_t n = squish_archive_count(a);
    for (uint64_t i = 0; i < n; i++) {
        squish_archive_entry e;
        squish_archive_stat(a, i, &e);
        printf("%s%s (%llu bytes)\n", e.path, e.is_dir ? "/" : "",
               (unsigned long long)e.size);
    }
    void *buf; size_t len;
    if (squish_archive_extract_path(a, "2026/trip/cover.jpg", &buf, &len) == SQUISH_OK) {
        /* ... use buf[0..len) ... */
        squish_free(buf);
    }
    squish_archive_close(a);
}

Linking recipes

# against the installed shared library
cc app.c $(pkg-config --cflags --libs squish)

# against the in-tree static library
cc app.c -I/path/to/squish /path/to/squish/libsquish.a -lm

# Windows, consuming squish.dll built via `make dll` (import lib libsquish.dll.a)
x86_64-w64-mingw32-gcc app.c -DSQUISH_DLL -lsquish -L/path/to/import/lib

From Python, load libsquish.so with ctypes directly — examples/example.py is a complete, copyable wrapper.

ABI and format stability

  • The shared library uses semantic versioning; soname libsquish.so.1. Functions are added, never changed, within a major version.
  • There is one on-disk format, identified by the magic SQSH and a version field. The model constants in squish.c are the format: archives are only decodable by a build with identical constants. Readers reject other magics or versions with SQUISH_E_FORMAT.