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());
}
Platform support
Every binding runs on Linux (glibc and Alpine/musl, x64 and arm64) and macOS; Windows is unsupported, since snapdir is Unix-only.
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.