snapdir for C/C++

The C/C++ binding is a header-only RAII wrapper (snapdir.hpp) over the snapdir C ABI (snapdir.h), which in turn wraps the canonical snapdir-api Rust core. Manifests and snapshot IDs are bit-identical to the snapdir CLI and to every other binding — a <snapshot-id> you compute here is the exact 64-char lowercase hex string you get from snapdir id.

Install

There is no C++ package registry. The published artifact is the snapdir-ffi source crate on crates.io — you build the static library, generate the C header with cbindgen, and compile your program against it with snapdir.hpp (a build-from-source binding):

cargo build --release                       # -> target/release/libsnapdir_ffi.a
cbindgen --config cbindgen.toml --crate snapdir-ffi --output snapdir.h .
g++ -std=c++20 app.cpp -I. -L. -lsnapdir_ffi -lpthread -ldl -lm -o app

You need a C17 compiler (C20 tested), a Rust toolchain, and cbindgen. See Install snapdir for C/C++ for details.

Usage

Synchronous id / manifest; async push / pull / fetch / diff return a std::future<T> that resolves or throws on .get(). The store URI is always an argument — file://$PWD/store for local, gs://bucket/prefix for Google Cloud Storage (also S3, Backblaze B2, and SSH).

#include <snapdir.hpp>
#include <iostream>

int main() {
    const std::string store = "file:///tmp/store";

    // Synchronous: the 64-char snapshot id and the full manifest.
    const std::string id = snapdir::id("./my-dir");
    snapdir::Manifest m = snapdir::manifest("./my-dir");
    for (const snapdir::ManifestEntry &e : m.entries) {
        // e.type (PathType), e.perm, e.checksum, e.size, e.path
    }
    // snapdir::id_from_manifest(m) == id

    // Options mirror the C ABI defaults.
    snapdir::ManifestOptions opts;
    opts.no_follow = true;                  // do not follow symlinks
    opts.exclude   = std::string("\\.tmp$"); // extended-regex exclusion

    // Async transfer ops.
    const std::string pushed = snapdir::push("./my-dir", store).get();  // <snapshot-id>
    snapdir::pull(pushed, store, "./restored").get();
    snapdir::fetch(pushed, store).get();    // fetch into the local catalog

    // Diff two stores; each ref follows the store@id convention
    // (the last '@' splits the store URI from the 64-hex id).
    std::vector<snapdir::DiffEntry> changes =
        snapdir::diff("file:///tmp/store-a", "file:///tmp/store-b").get();
    for (const auto &e : changes) {
        // e.status is one of A / D / M / = ; e.path is the entry path
        std::cout << static_cast<char>(e.status) << '\t' << e.path << '\n';
    }
}

Errors

Every operation throws snapdir::Error (a std::runtime_error) on failure. The RAII guards release every C allocation even when an exception unwinds. Error::code() returns one of the stable, SCREAMING_SNAKE_CASE codes shared across all bindings:

try {
    auto id = snapdir::id("/no/such/path");
} catch (const snapdir::Error &e) {
    // e.code() is one of: IO_ERROR, HASH_MISMATCH, STORE_ERROR, IN_FLUX,
    // CATALOG_ERROR, INVALID_ID, INVALID_STORE, CONFLICT.
    std::fprintf(stderr, "snapdir error [%s]: %s\n", e.code().c_str(), e.what());
}
Code Meaning
IO_ERROR A filesystem read/write failed.
HASH_MISMATCH Stored content did not match its expected checksum.
STORE_ERROR The object store rejected or failed a request.
IN_FLUX The source tree changed while being snapshotted.
CATALOG_ERROR The local catalog is missing or corrupt.
INVALID_ID The snapshot id is not 64-char lowercase hex.
INVALID_STORE The store URI is malformed or unsupported.
CONFLICT A concurrent write conflicted with this operation.

Platform support

Every binding runs on Linux (glibc and Alpine/musl, x64 and arm64) and macOS; Windows is unsupported, since snapdir is Unix-only.

Platform Supported
Linux glibc (x64 + arm64) yes
Linux musl / Alpine (x64 + arm64) yes
macOS yes
Windows no

The one binding-wide exception is Java, which is glibc-only today (musl support is planned for 1.11.1). The C/C++ binding builds and links on glibc and musl alike.