diff --git a/src/libstore-c/meson.build b/src/libstore-c/meson.build index c6b6174c7..c81235bf1 100644 --- a/src/libstore-c/meson.build +++ b/src/libstore-c/meson.build @@ -35,6 +35,8 @@ include_dirs = [ include_directories('.') ] headers = files( 'nix_api_store.h', + 'nix_api_store/derivation.h', + 'nix_api_store/store_path.h', ) # TODO don't install this once tests don't use it and/or move the header into `libstore`, non-`c` diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index b95e5b749..eef4cf3ba 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -10,6 +10,10 @@ #include "nix/store/local-fs-store.hh" #include "nix/store/globals.hh" +#include "nix/util/base-nix-32.hh" + +#include +#include extern "C" { @@ -218,6 +222,44 @@ StorePath * nix_store_path_clone(const StorePath * p) return new StorePath{p->path}; } +nix_store_path_hash_path nix_store_path_hash(const StorePath * store_path) +{ + auto hashPart = store_path->path.hashPart(); + // Decode from Nix32 (base32) encoding to raw bytes + auto decoded = nix::BaseNix32::decode(hashPart); + + nix_store_path_hash_path result; + assert(decoded.size() == 20); + std::memcpy(result.bytes, decoded.data(), 20); + return result; +} + +StorePath * nix_store_create_from_parts( + nix_c_context * context, const nix_store_path_hash_path * hash, const char * name, size_t name_len) +{ + if (context) + context->last_err_code = NIX_OK; + try { + // Encode the 20 raw bytes to Nix32 (base32) format + auto hashStr = + nix::BaseNix32::encode(std::span(reinterpret_cast(hash->bytes), 20)); + + // Construct the store path basename: - + std::string baseName; + baseName += hashStr; + baseName += "-"; + baseName += std::string_view{name, name_len}; + + return new StorePath{nix::StorePath(std::move(baseName))}; + } + NIXC_CATCH_ERRS_NULL +} + +nix_derivation * nix_derivation_clone(const nix_derivation * d) +{ + return new nix_derivation{d->drv}; +} + nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json) { if (context) @@ -228,6 +270,20 @@ nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store NIXC_CATCH_ERRS_NULL } +nix_err nix_derivation_to_json( + nix_c_context * context, const nix_derivation * drv, nix_get_string_callback callback, void * userdata) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto result = static_cast(drv->drv).dump(); + if (callback) { + callback(result.data(), result.size(), userdata); + } + } + NIXC_CATCH_ERRS +} + StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation) { if (context) @@ -252,4 +308,73 @@ nix_err nix_store_copy_closure(nix_c_context * context, Store * srcStore, Store NIXC_CATCH_ERRS } +nix_err nix_store_drv_from_path( + nix_c_context * context, + Store * store, + const StorePath * path, + void (*callback)(void * userdata, const nix_derivation * drv), + void * userdata) +{ + if (context) + context->last_err_code = NIX_OK; + try { + nix::Derivation drv = store->ptr->derivationFromPath(path->path); + if (callback) { + const nix_derivation tmp{drv}; + callback(userdata, &tmp); + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_store_query_path_info( + nix_c_context * context, + Store * store, + const StorePath * store_path, + void * userdata, + nix_get_string_callback callback) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto info = store->ptr->queryPathInfo(store_path->path); + if (callback) { + auto result = info->toJSON(&*store->ptr, true).dump(); + callback(result.data(), result.size(), userdata); + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_store_build_paths( + nix_c_context * context, + Store * store, + const StorePath ** store_paths, + unsigned int num_store_paths, + void (*callback)(void * userdata, const char * path, const char * result), + void * userdata) +{ + if (context) + context->last_err_code = NIX_OK; + try { + std::span paths_span(store_paths, num_store_paths); + + std::vector derived_paths; + for (const StorePath * store_path : paths_span) { + derived_paths.push_back(nix::SingleDerivedPath::Opaque{store_path->path}); + } + + auto results = store->ptr->buildPathsWithResults(derived_paths); + for (auto & result : results) { + if (callback) { + callback( + userdata, + result.path.to_string(store->ptr->config).c_str(), + static_cast(result).dump().c_str()); + } + } + } + NIXC_CATCH_ERRS +} + } // extern "C" diff --git a/src/libstore-c/nix_api_store.h b/src/libstore-c/nix_api_store.h index 964f6d6d5..b56bafbc9 100644 --- a/src/libstore-c/nix_api_store.h +++ b/src/libstore-c/nix_api_store.h @@ -12,6 +12,8 @@ */ #include "nix_api_util.h" +#include "nix_api_store/store_path.h" +#include "nix_api_store/derivation.h" #include #ifdef __cplusplus @@ -21,10 +23,6 @@ extern "C" { /** @brief Reference to a Nix store */ typedef struct Store Store; -/** @brief Nix store path */ -typedef struct StorePath StorePath; -/** @brief Nix Derivation */ -typedef struct nix_derivation nix_derivation; /** * @brief Initializes the Nix store library @@ -108,7 +106,7 @@ nix_err nix_store_get_storedir(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data); /** - * @brief Parse a Nix store path into a StorePath + * @brief Parse a Nix store path that includes the store dir into a StorePath * * @note Don't forget to free this path using nix_store_path_free()! * @param[out] context Optional, stores error information @@ -118,30 +116,6 @@ nix_store_get_storedir(nix_c_context * context, Store * store, nix_get_string_ca */ StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path); -/** - * @brief Get the path name (e.g. "name" in /nix/store/...-name) - * - * @param[in] store_path the path to get the name from - * @param[in] callback called with the name - * @param[in] user_data arbitrary data, passed to the callback when it's called. - */ -void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data); - -/** - * @brief Copy a StorePath - * - * @param[in] p the path to copy - * @return a new StorePath - */ -StorePath * nix_store_path_clone(const StorePath * p); - -/** @brief Deallocate a StorePath - * - * Does not fail. - * @param[in] p the path to free - */ -void nix_store_path_free(StorePath * p); - /** * @brief Check if a StorePath is valid (i.e. that corresponding store object and its closure of references exists in * the store) @@ -214,6 +188,12 @@ nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_cal /** * @brief Create a `nix_derivation` from a JSON representation of that derivation. * + * @note Unlike `nix_derivation_to_json`, this needs a `Store`. This is because + * over time we expect the internal representation of derivations in Nix to + * differ from accepted derivation formats. The store argument is here to help + * any logic needed to convert from JSON to the internal representation, in + * excess of just parsing. + * * @param[out] context Optional, stores error information. * @param[in] store nix store reference. * @param[in] json JSON of the derivation as a string. @@ -229,14 +209,6 @@ nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store */ StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation); -/** - * @brief Deallocate a `nix_derivation` - * - * Does not fail. - * @param[in] drv the derivation to free - */ -void nix_derivation_free(nix_derivation * drv); - /** * @brief Copy the closure of `path` from `srcStore` to `dstStore`. * @@ -276,6 +248,61 @@ nix_err nix_store_get_fs_closure( void * userdata, void (*callback)(nix_c_context * context, void * userdata, const StorePath * store_path)); +/** + * @brief Returns the derivation associated with the store path + * + * @note The callback borrows the Derivation only for the duration of the call. + * + * @param[out] context Optional, stores error information + * @param[in] store The nix store + * @param[in] path The nix store path + * @param[in] callback The callback to call + * @param[in] userdata The userdata to pass to the callback + */ +nix_err nix_store_drv_from_path( + nix_c_context * context, + Store * store, + const StorePath * path, + void (*callback)(void * userdata, const nix_derivation * drv), + void * userdata); + +/** + * @brief Queries for the nix store path info JSON. + * + * @param[out] context Optional, stores error information + * @param[in] store nix store reference + * @param[in] path A store path + * @param[in] userdata The data to pass to the callback + * @param[in] callback Called for when the path info is resolved + */ +nix_err nix_store_query_path_info( + nix_c_context * context, + Store * store, + const StorePath * store_path, + void * userdata, + nix_get_string_callback callback); + +/** + * @brief Builds the paths, if they are a derivation then they get built. + * + * @note Path and result for the callback only exist for the lifetime of + * the call. Result is a string containing the build result in JSON. + * + * @param[out] context Optional, stores error information + * @param[in] store nix store reference + * @param[in] store_paths Pointer to list of nix store paths + * @param[in] num_store_paths Number of nix store paths + * @param[in] callback The callback to trigger for build results + * @param[in] userdata User data to pass to the callback + */ +nix_err nix_store_build_paths( + nix_c_context * context, + Store * store, + const StorePath ** store_paths, + unsigned int num_store_paths, + void (*callback)(void * userdata, const char * path, const char * result), + void * userdata); + // cffi end #ifdef __cplusplus } diff --git a/src/libstore-c/nix_api_store/derivation.h b/src/libstore-c/nix_api_store/derivation.h new file mode 100644 index 000000000..239ffd52f --- /dev/null +++ b/src/libstore-c/nix_api_store/derivation.h @@ -0,0 +1,57 @@ +#ifndef NIX_API_STORE_DERIVATION_H +#define NIX_API_STORE_DERIVATION_H +/** + * @defgroup libstore_derivation Derivation + * @ingroup libstore + * @brief Derivation operations that don't require a Store + * @{ + */ +/** @file + * @brief Derivation operations + */ + +#include "nix_api_util.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** @brief Nix Derivation */ +typedef struct nix_derivation nix_derivation; + +/** + * @brief Copy a `nix_derivation` + * + * @param[in] d the derivation to copy + * @return a new `nix_derivation` + */ +nix_derivation * nix_derivation_clone(const nix_derivation * d); + +/** + * @brief Deallocate a `nix_derivation` + * + * Does not fail. + * @param[in] drv the derivation to free + */ +void nix_derivation_free(nix_derivation * drv); + +/** + * @brief Gets the derivation as a JSON string + * + * @param[out] context Optional, stores error information + * @param[in] drv The derivation + * @param[in] callback Called with the JSON string + * @param[in] userdata Arbitrary data passed to the callback + */ +nix_err nix_derivation_to_json( + nix_c_context * context, const nix_derivation * drv, nix_get_string_callback callback, void * userdata); + +// cffi end +#ifdef __cplusplus +} +#endif +/** + * @} + */ +#endif // NIX_API_STORE_DERIVATION_H diff --git a/src/libstore-c/nix_api_store/store_path.h b/src/libstore-c/nix_api_store/store_path.h new file mode 100644 index 000000000..140cc4521 --- /dev/null +++ b/src/libstore-c/nix_api_store/store_path.h @@ -0,0 +1,93 @@ +#ifndef NIX_API_STORE_STORE_PATH_H +#define NIX_API_STORE_STORE_PATH_H +/** + * @defgroup libstore_storepath StorePath + * @ingroup libstore + * @brief Store path operations that don't require a Store + * @{ + */ +/** @file + * @brief Store path operations + */ + +#include +#include + +#include "nix_api_util.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** @brief Nix store path */ +typedef struct StorePath StorePath; + +/** + * @brief Copy a StorePath + * + * @param[in] p the path to copy + * @return a new StorePath + */ +StorePath * nix_store_path_clone(const StorePath * p); + +/** @brief Deallocate a StorePath + * + * Does not fail. + * @param[in] p the path to free + */ +void nix_store_path_free(StorePath * p); + +/** + * @brief Get the path name (e.g. "" in /nix/store/-) + * + * @param[in] store_path the path to get the name from + * @param[in] callback called with the name + * @param[in] user_data arbitrary data, passed to the callback when it's called. + */ +void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data); + +/** + * @brief A store path hash + * + * Once decoded from "nix32" encoding, a store path hash is 20 raw bytes. + */ +typedef struct nix_store_path_hash_path +{ + uint8_t bytes[20]; +} nix_store_path_hash_path; + +/** + * @brief Get the path hash (e.g. "" in /nix/store/-) + * + * The hash is returned as raw bytes, decoded from "nix32" encoding. + * + * @param[in] store_path the path to get the hash from + * @return the decoded hash as 20 raw bytes + */ +nix_store_path_hash_path nix_store_path_hash(const StorePath * store_path); + +/** + * @brief Create a StorePath from its constituent parts (hash and name) + * + * This function constructs a store path from a hash and name, without needing + * a Store reference or the store directory prefix. + * + * @note Don't forget to free this path using nix_store_path_free()! + * @param[out] context Optional, stores error information + * @param[in] hash The store path hash (20 raw bytes) + * @param[in] name The store path name (the part after the hash) + * @param[in] name_len Length of the name string + * @return owned store path, NULL on error + */ +StorePath * nix_store_create_from_parts( + nix_c_context * context, const nix_store_path_hash_path * hash, const char name[/*name_len*/], size_t name_len); + +// cffi end +#ifdef __cplusplus +} +#endif +/** + * @} + */ +#endif // NIX_API_STORE_STORE_PATH_H diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index bf411053a..b451722a6 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -92,6 +92,63 @@ TEST_F(nix_api_store_test, DoesNotCrashWhenContextIsNull) nix_store_path_free(path); } +TEST_F(nix_api_store_test, nix_store_path_hash) +{ + StorePath * path = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + ASSERT_NE(path, nullptr); + + nix_store_path_hash_path hash = nix_store_path_hash(path); + + // Verify it's 20 bytes + static_assert(sizeof(hash.bytes) == 20); + + // The hash should be non-zero + bool allZero = true; + for (int i = 0; i < 20; i++) { + if (hash.bytes[i] != 0) { + allZero = false; + break; + } + } + ASSERT_FALSE(allZero); + + nix_store_path_free(path); +} + +TEST_F(nix_api_store_test, nix_store_create_from_parts_roundtrip) +{ + // Parse a path + StorePath * original = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + EXPECT_NE(original, nullptr); + + // Get its hash + nix_store_path_hash_path hash = nix_store_path_hash(original); + + // Get its name + std::string name; + nix_store_path_name(original, OBSERVE_STRING(name)); + + // Reconstruct from parts + StorePath * reconstructed = nix_store_create_from_parts(ctx, &hash, name.c_str(), name.size()); + assert_ctx_ok(); + ASSERT_NE(reconstructed, nullptr); + + // Should be equal + EXPECT_EQ(original->path, reconstructed->path); + + nix_store_path_free(original); + nix_store_path_free(reconstructed); +} + +TEST_F(nix_api_store_test, nix_store_create_from_parts_invalid_name) +{ + nix_store_path_hash_path hash = {}; + // Invalid name with spaces + StorePath * path = nix_store_create_from_parts(ctx, &hash, "invalid name", 12); + ASSERT_EQ(path, nullptr); + ASSERT_EQ(nix_err_code(ctx), NIX_ERR_NIX_ERROR); +} + TEST_F(nix_api_store_test, get_version) { std::string str;