From 714d2a8871a0b23e596596e592392111f8d593e8 Mon Sep 17 00:00:00 2001 From: Sander Date: Thu, 26 Mar 2026 02:53:58 +0100 Subject: [PATCH 1/9] libstore-c: add accessor-based path info API Exposes store path metadata via an opaque handle with getters. --- src/libstore-c/meson.build | 1 + src/libstore-c/nix_api_store.cc | 103 +++++++++++++++++++ src/libstore-c/nix_api_store.h | 14 +++ src/libstore-c/nix_api_store/path_info.h | 125 +++++++++++++++++++++++ src/libstore-c/nix_api_store_internal.h | 6 ++ src/libstore-tests/nix_api_store.cc | 72 +++++++++++++ 6 files changed, 321 insertions(+) create mode 100644 src/libstore-c/nix_api_store/path_info.h diff --git a/src/libstore-c/meson.build b/src/libstore-c/meson.build index 600de4d2ea61..e4e5b8ec96e5 100644 --- a/src/libstore-c/meson.build +++ b/src/libstore-c/meson.build @@ -36,6 +36,7 @@ include_dirs = [ include_directories('.') ] headers = files( 'nix_api_store.h', 'nix_api_store/derivation.h', + 'nix_api_store/path_info.h', 'nix_api_store/store_path.h', ) diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index c0b29625440d..c104a362e980 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -16,6 +16,7 @@ #include "nix/util/base-nix-32.hh" #include "nix/store/globals.hh" +#include "nix/store/content-address.hh" extern "C" { @@ -381,4 +382,106 @@ nix_err nix_store_copy_path( NIXC_CATCH_ERRS } +PathInfo * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto info = store->ptr->queryPathInfo(path->path); + return new PathInfo{info}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_path_info_free(PathInfo * path_info) +{ + delete path_info; +} + +nix_err nix_path_info_get_nar_hash( + nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto res = path_info->info->narHash.to_string(nix::HashFormat::Nix32, true); + return call_nix_get_string_callback(res, callback, user_data); + } + NIXC_CATCH_ERRS +} + +uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * path_info) +{ + if (context) + context->last_err_code = NIX_OK; + try { + return path_info->info->narSize; + } + NIXC_CATCH_ERRS_RES(0); +} + +nix_err nix_path_info_get_references( + nix_c_context * context, + const PathInfo * path_info, + void * user_data, + void (*callback)(void * user_data, const StorePath * store_path)) +{ + if (context) + context->last_err_code = NIX_OK; + try { + if (callback) { + for (const auto & ref : path_info->info->references) { + const StorePath tmp{ref}; + callback(user_data, &tmp); + } + } + } + NIXC_CATCH_ERRS +} + +StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * path_info) +{ + if (context) + context->last_err_code = NIX_OK; + try { + if (path_info->info->deriver) + return new StorePath{*path_info->info->deriver}; + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_path_info_get_sigs( + nix_c_context * context, + const PathInfo * path_info, + void * user_data, + void (*callback)(void * user_data, const char * sig, unsigned int sig_len)) +{ + if (context) + context->last_err_code = NIX_OK; + try { + if (callback) { + for (const auto & sig : path_info->info->sigs) { + auto s = sig.to_string(); + callback(user_data, s.data(), s.size()); + } + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_path_info_get_ca( + nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data) +{ + if (context) + context->last_err_code = NIX_OK; + try { + if (path_info->info->ca && callback) { + auto res = renderContentAddress(*path_info->info->ca); + return call_nix_get_string_callback(res, callback, user_data); + } + } + NIXC_CATCH_ERRS +} + } // extern "C" diff --git a/src/libstore-c/nix_api_store.h b/src/libstore-c/nix_api_store.h index e60a7d6c0f7c..a8c912cd262c 100644 --- a/src/libstore-c/nix_api_store.h +++ b/src/libstore-c/nix_api_store.h @@ -14,6 +14,7 @@ #include "nix_api_util.h" #include "nix_api_store/store_path.h" #include "nix_api_store/derivation.h" +#include "nix_api_store/path_info.h" #include #ifdef __cplusplus @@ -283,6 +284,19 @@ StorePath * nix_store_query_path_from_hash_part(nix_c_context * context, Store * nix_err nix_store_copy_path( nix_c_context * context, Store * srcStore, Store * dstStore, const StorePath * path, bool repair, bool checkSigs); +/** + * @brief Query metadata about a store path + * + * The path must be valid in the store; otherwise an error is returned. + * + * @note Don't forget to free this with nix_path_info_free()! + * @param[out] context Optional, stores error information + * @param[in] store Nix store reference + * @param[in] path The store path to query + * @return owned PathInfo, NULL on error + */ +PathInfo * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path); + // cffi end #ifdef __cplusplus } diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h new file mode 100644 index 000000000000..a74b6e2c807a --- /dev/null +++ b/src/libstore-c/nix_api_store/path_info.h @@ -0,0 +1,125 @@ +#ifndef NIX_API_STORE_PATH_INFO_H +#define NIX_API_STORE_PATH_INFO_H +/** + * @defgroup libstore_pathinfo PathInfo + * @ingroup libstore + * @brief Store path metadata (narHash, references, signatures, etc.) + * @{ + */ +/** @file + * @brief Path info operations for querying store object metadata + */ + +#include + +#include "nix_api_util.h" +#include "nix_api_store/store_path.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** @brief Opaque handle to store path metadata */ +typedef struct PathInfo PathInfo; + +/** + * @brief Deallocate a PathInfo + * + * Does not fail. + * @param[in] path_info the PathInfo to free + */ +void nix_path_info_free(PathInfo * path_info); + +/** + * @brief Get the NAR hash of a store path + * + * Returns the hash as a string with algorithm prefix in Nix base-32 encoding, + * e.g. "sha256:1b8m03r63zqhnjf7l5nh...". This is the format used in NARINFO files. + * + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @param[in] callback called with the hash string + * @param[in] user_data arbitrary data, passed to the callback when it's called + * @return NIX_OK on success, error code on failure + */ +nix_err nix_path_info_get_nar_hash( + nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data); + +/** + * @brief Get the NAR size of a store path + * + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @return NAR size in bytes, 0 if unknown + */ +uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * path_info); + +/** + * @brief Iterate over the references of a store path + * + * Calls the callback once for each reference. The StorePath passed to the + * callback is borrowed and only valid for the duration of the callback. + * + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @param[in] user_data arbitrary data, passed to the callback + * @param[in] callback called for each referenced store path + * @return NIX_OK on success, error code on failure + */ +nix_err nix_path_info_get_references( + nix_c_context * context, + const PathInfo * path_info, + void * user_data, + void (*callback)(void * user_data, const StorePath * store_path)); + +/** + * @brief Get the deriver of a store path + * + * @note Don't forget to free the result with nix_store_path_free()! + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @return owned StorePath of the deriver, or NULL if no deriver is known + */ +StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * path_info); + +/** + * @brief Iterate over the signatures of a store path + * + * Calls the callback once for each signature string (format: "keyName:base64sig"). + * + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @param[in] user_data arbitrary data, passed to the callback + * @param[in] callback called for each signature string + * @return NIX_OK on success, error code on failure + */ +nix_err nix_path_info_get_sigs( + nix_c_context * context, + const PathInfo * path_info, + void * user_data, + void (*callback)(void * user_data, const char * sig, unsigned int sig_len)); + +/** + * @brief Get the content address of a store path, if any + * + * If the path is content-addressed, calls the callback with the rendered + * content address string. If not content-addressed, the callback is not called. + * + * @param[out] context Optional, stores error information + * @param[in] path_info the PathInfo to read from + * @param[in] callback called with the content address string, if present + * @param[in] user_data arbitrary data, passed to the callback when it's called + * @return NIX_OK on success, error code on failure + */ +nix_err nix_path_info_get_ca( + nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data); + +// cffi end +#ifdef __cplusplus +} +#endif +/** + * @} + */ +#endif // NIX_API_STORE_PATH_INFO_H diff --git a/src/libstore-c/nix_api_store_internal.h b/src/libstore-c/nix_api_store_internal.h index 712d96488a57..f988c670cb31 100644 --- a/src/libstore-c/nix_api_store_internal.h +++ b/src/libstore-c/nix_api_store_internal.h @@ -2,6 +2,7 @@ #define NIX_API_STORE_INTERNAL_H #include "nix/store/store-api.hh" #include "nix/store/derivations.hh" +#include "nix/store/path-info.hh" extern "C" { @@ -20,6 +21,11 @@ struct nix_derivation nix::Derivation drv; }; +struct PathInfo +{ + nix::ref info; +}; + } // extern "C" #endif diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index 0162684daf4b..4a7fa3e5ba8e 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -977,4 +977,76 @@ TEST_F(nix_api_store_test, nix_derivation_clone) nix_derivation_free(drv2); } +TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) +{ + PathInfo * info = nix_store_query_path_info(ctx, store, outPath); + assert_ctx_ok(); + ASSERT_NE(info, nullptr); + + std::string narHash; + auto ret = nix_path_info_get_nar_hash(ctx, info, OBSERVE_STRING(narHash)); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + ASSERT_FALSE(narHash.empty()); + ASSERT_EQ(narHash.substr(0, 7), "sha256:"); + + auto narSize = nix_path_info_get_nar_size(ctx, info); + assert_ctx_ok(); + ASSERT_GT(narSize, 0u); + + // May be empty for this simple derivation + std::vector refs; + auto refCb = LambdaAdapter{.fun = [&](const StorePath * refPath) { + std::string name; + nix_store_path_name(refPath, OBSERVE_STRING(name)); + refs.push_back(name); + }}; + ret = nix_path_info_get_references( + ctx, info, static_cast(&refCb), decltype(refCb)::call_void); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + std::vector sigs; + auto sigCb = LambdaAdapter{.fun = [&](const char * sig, unsigned int sig_len) { sigs.emplace_back(sig, sig_len); }}; + ret = nix_path_info_get_sigs( + ctx, info, static_cast(&sigCb), decltype(sigCb)::call_void); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + // This is a CA derivation, so ca should be present + std::string ca; + ret = nix_path_info_get_ca(ctx, info, OBSERVE_STRING(ca)); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver) +{ + PathInfo * info = nix_store_query_path_info(ctx, store, outPath); + assert_ctx_ok(); + ASSERT_NE(info, nullptr); + + // The output was built from a derivation, so deriver should be set + StorePath * deriver = nix_path_info_get_deriver(ctx, info); + assert_ctx_ok(); + ASSERT_NE(deriver, nullptr); + + nix_store_path_free(deriver); + nix_path_info_free(info); +} + +TEST_F(nix_api_store_test, nix_store_query_path_info_invalid_path) +{ + StorePath * path = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); + ASSERT_NE(path, nullptr); + + PathInfo * info = nix_store_query_path_info(ctx, store, path); + ASSERT_EQ(info, nullptr); + ASSERT_NE(nix_err_code(ctx), NIX_OK); + + nix_store_path_free(path); +} + } // namespace nixC From eefdfbc90ea0b17271b024b6f590882f9a3c2da4 Mon Sep 17 00:00:00 2001 From: Sander Date: Mon, 13 Apr 2026 15:39:14 +0100 Subject: [PATCH 2/9] libstore-c: rename PathInfo to nix_path_info --- src/libstore-c/nix_api_store.cc | 18 ++++++------- src/libstore-c/nix_api_store.h | 4 +-- src/libstore-c/nix_api_store/path_info.h | 32 ++++++++++++------------ src/libstore-c/nix_api_store_internal.h | 2 +- src/libstore-tests/nix_api_store.cc | 6 ++--- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index c104a362e980..26fdd6bced48 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -382,24 +382,24 @@ nix_err nix_store_copy_path( NIXC_CATCH_ERRS } -PathInfo * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path) +nix_path_info * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path) { if (context) context->last_err_code = NIX_OK; try { auto info = store->ptr->queryPathInfo(path->path); - return new PathInfo{info}; + return new nix_path_info{info}; } NIXC_CATCH_ERRS_NULL } -void nix_path_info_free(PathInfo * path_info) +void nix_path_info_free(nix_path_info * path_info) { delete path_info; } nix_err nix_path_info_get_nar_hash( - nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data) + nix_c_context * context, const nix_path_info * path_info, nix_get_string_callback callback, void * user_data) { if (context) context->last_err_code = NIX_OK; @@ -410,7 +410,7 @@ nix_err nix_path_info_get_nar_hash( NIXC_CATCH_ERRS } -uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * path_info) +uint64_t nix_path_info_get_nar_size(nix_c_context * context, const nix_path_info * path_info) { if (context) context->last_err_code = NIX_OK; @@ -422,7 +422,7 @@ uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * pa nix_err nix_path_info_get_references( nix_c_context * context, - const PathInfo * path_info, + const nix_path_info * path_info, void * user_data, void (*callback)(void * user_data, const StorePath * store_path)) { @@ -439,7 +439,7 @@ nix_err nix_path_info_get_references( NIXC_CATCH_ERRS } -StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * path_info) +StorePath * nix_path_info_get_deriver(nix_c_context * context, const nix_path_info * path_info) { if (context) context->last_err_code = NIX_OK; @@ -453,7 +453,7 @@ StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * nix_err nix_path_info_get_sigs( nix_c_context * context, - const PathInfo * path_info, + const nix_path_info * path_info, void * user_data, void (*callback)(void * user_data, const char * sig, unsigned int sig_len)) { @@ -471,7 +471,7 @@ nix_err nix_path_info_get_sigs( } nix_err nix_path_info_get_ca( - nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data) + nix_c_context * context, const nix_path_info * path_info, nix_get_string_callback callback, void * user_data) { if (context) context->last_err_code = NIX_OK; diff --git a/src/libstore-c/nix_api_store.h b/src/libstore-c/nix_api_store.h index a8c912cd262c..e1f45fae7389 100644 --- a/src/libstore-c/nix_api_store.h +++ b/src/libstore-c/nix_api_store.h @@ -293,9 +293,9 @@ nix_err nix_store_copy_path( * @param[out] context Optional, stores error information * @param[in] store Nix store reference * @param[in] path The store path to query - * @return owned PathInfo, NULL on error + * @return owned nix_path_info, NULL on error */ -PathInfo * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path); +nix_path_info * nix_store_query_path_info(nix_c_context * context, Store * store, const StorePath * path); // cffi end #ifdef __cplusplus diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index a74b6e2c807a..df737f727229 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -21,15 +21,15 @@ extern "C" { // cffi start /** @brief Opaque handle to store path metadata */ -typedef struct PathInfo PathInfo; +typedef struct nix_path_info nix_path_info; /** - * @brief Deallocate a PathInfo + * @brief Deallocate a nix_path_info * * Does not fail. - * @param[in] path_info the PathInfo to free + * @param[in] path_info the nix_path_info to free */ -void nix_path_info_free(PathInfo * path_info); +void nix_path_info_free(nix_path_info * path_info); /** * @brief Get the NAR hash of a store path @@ -38,22 +38,22 @@ void nix_path_info_free(PathInfo * path_info); * e.g. "sha256:1b8m03r63zqhnjf7l5nh...". This is the format used in NARINFO files. * * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @param[in] callback called with the hash string * @param[in] user_data arbitrary data, passed to the callback when it's called * @return NIX_OK on success, error code on failure */ nix_err nix_path_info_get_nar_hash( - nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data); + nix_c_context * context, const nix_path_info * path_info, nix_get_string_callback callback, void * user_data); /** * @brief Get the NAR size of a store path * * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @return NAR size in bytes, 0 if unknown */ -uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * path_info); +uint64_t nix_path_info_get_nar_size(nix_c_context * context, const nix_path_info * path_info); /** * @brief Iterate over the references of a store path @@ -62,14 +62,14 @@ uint64_t nix_path_info_get_nar_size(nix_c_context * context, const PathInfo * pa * callback is borrowed and only valid for the duration of the callback. * * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @param[in] user_data arbitrary data, passed to the callback * @param[in] callback called for each referenced store path * @return NIX_OK on success, error code on failure */ nix_err nix_path_info_get_references( nix_c_context * context, - const PathInfo * path_info, + const nix_path_info * path_info, void * user_data, void (*callback)(void * user_data, const StorePath * store_path)); @@ -78,10 +78,10 @@ nix_err nix_path_info_get_references( * * @note Don't forget to free the result with nix_store_path_free()! * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @return owned StorePath of the deriver, or NULL if no deriver is known */ -StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * path_info); +StorePath * nix_path_info_get_deriver(nix_c_context * context, const nix_path_info * path_info); /** * @brief Iterate over the signatures of a store path @@ -89,14 +89,14 @@ StorePath * nix_path_info_get_deriver(nix_c_context * context, const PathInfo * * Calls the callback once for each signature string (format: "keyName:base64sig"). * * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @param[in] user_data arbitrary data, passed to the callback * @param[in] callback called for each signature string * @return NIX_OK on success, error code on failure */ nix_err nix_path_info_get_sigs( nix_c_context * context, - const PathInfo * path_info, + const nix_path_info * path_info, void * user_data, void (*callback)(void * user_data, const char * sig, unsigned int sig_len)); @@ -107,13 +107,13 @@ nix_err nix_path_info_get_sigs( * content address string. If not content-addressed, the callback is not called. * * @param[out] context Optional, stores error information - * @param[in] path_info the PathInfo to read from + * @param[in] path_info the nix_path_info to read from * @param[in] callback called with the content address string, if present * @param[in] user_data arbitrary data, passed to the callback when it's called * @return NIX_OK on success, error code on failure */ nix_err nix_path_info_get_ca( - nix_c_context * context, const PathInfo * path_info, nix_get_string_callback callback, void * user_data); + nix_c_context * context, const nix_path_info * path_info, nix_get_string_callback callback, void * user_data); // cffi end #ifdef __cplusplus diff --git a/src/libstore-c/nix_api_store_internal.h b/src/libstore-c/nix_api_store_internal.h index f988c670cb31..a8f78d801288 100644 --- a/src/libstore-c/nix_api_store_internal.h +++ b/src/libstore-c/nix_api_store_internal.h @@ -21,7 +21,7 @@ struct nix_derivation nix::Derivation drv; }; -struct PathInfo +struct nix_path_info { nix::ref info; }; diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index 4a7fa3e5ba8e..bd6e6d73098b 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -979,7 +979,7 @@ TEST_F(nix_api_store_test, nix_derivation_clone) TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) { - PathInfo * info = nix_store_query_path_info(ctx, store, outPath); + nix_path_info * info = nix_store_query_path_info(ctx, store, outPath); assert_ctx_ok(); ASSERT_NE(info, nullptr); @@ -1024,7 +1024,7 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver) { - PathInfo * info = nix_store_query_path_info(ctx, store, outPath); + nix_path_info * info = nix_store_query_path_info(ctx, store, outPath); assert_ctx_ok(); ASSERT_NE(info, nullptr); @@ -1042,7 +1042,7 @@ TEST_F(nix_api_store_test, nix_store_query_path_info_invalid_path) StorePath * path = nix_store_parse_path(ctx, store, (nixStoreDir + PATH_SUFFIX).c_str()); ASSERT_NE(path, nullptr); - PathInfo * info = nix_store_query_path_info(ctx, store, path); + nix_path_info * info = nix_store_query_path_info(ctx, store, path); ASSERT_EQ(info, nullptr); ASSERT_NE(nix_err_code(ctx), NIX_OK); From d13d5a5809154c47798ff4ba60f4640b3583b468 Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 15 Apr 2026 19:23:36 +0100 Subject: [PATCH 3/9] libstore-c: lint --- src/libstore-c/nix_api_store/path_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index df737f727229..12d10766b50c 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -35,7 +35,7 @@ void nix_path_info_free(nix_path_info * path_info); * @brief Get the NAR hash of a store path * * Returns the hash as a string with algorithm prefix in Nix base-32 encoding, - * e.g. "sha256:1b8m03r63zqhnjf7l5nh...". This is the format used in NARINFO files. + * e.g. "sha256:1b8m03r63zqhnjf7l5nh...". This is the format used in narinfo files. * * @param[out] context Optional, stores error information * @param[in] path_info the nix_path_info to read from From 1c9fee30383b169fce23dbe123bf984e364f6059 Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 15 Apr 2026 19:24:59 +0100 Subject: [PATCH 4/9] libstore-c: return nix_err in callbacks to allow early exit --- src/libstore-c/nix_api_store.cc | 12 ++++++++---- src/libstore-c/nix_api_store/path_info.h | 4 ++-- src/libstore-tests/nix_api_store.cc | 8 ++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index 26fdd6bced48..2a4cc3fe4c0c 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -424,7 +424,7 @@ nix_err nix_path_info_get_references( nix_c_context * context, const nix_path_info * path_info, void * user_data, - void (*callback)(void * user_data, const StorePath * store_path)) + nix_err (*callback)(void * user_data, const StorePath * store_path)) { if (context) context->last_err_code = NIX_OK; @@ -432,7 +432,9 @@ nix_err nix_path_info_get_references( if (callback) { for (const auto & ref : path_info->info->references) { const StorePath tmp{ref}; - callback(user_data, &tmp); + auto err = callback(user_data, &tmp); + if (err != NIX_OK) + return err; } } } @@ -455,7 +457,7 @@ nix_err nix_path_info_get_sigs( nix_c_context * context, const nix_path_info * path_info, void * user_data, - void (*callback)(void * user_data, const char * sig, unsigned int sig_len)) + nix_err (*callback)(void * user_data, const char * sig, unsigned int sig_len)) { if (context) context->last_err_code = NIX_OK; @@ -463,7 +465,9 @@ nix_err nix_path_info_get_sigs( if (callback) { for (const auto & sig : path_info->info->sigs) { auto s = sig.to_string(); - callback(user_data, s.data(), s.size()); + auto err = callback(user_data, s.data(), s.size()); + if (err != NIX_OK) + return err; } } } diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index 12d10766b50c..4159e3f0e418 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -71,7 +71,7 @@ nix_err nix_path_info_get_references( nix_c_context * context, const nix_path_info * path_info, void * user_data, - void (*callback)(void * user_data, const StorePath * store_path)); + nix_err (*callback)(void * user_data, const StorePath * store_path)); /** * @brief Get the deriver of a store path @@ -98,7 +98,7 @@ nix_err nix_path_info_get_sigs( nix_c_context * context, const nix_path_info * path_info, void * user_data, - void (*callback)(void * user_data, const char * sig, unsigned int sig_len)); + nix_err (*callback)(void * user_data, const char * sig, unsigned int sig_len)); /** * @brief Get the content address of a store path, if any diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index bd6e6d73098b..785a525a97c0 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -996,10 +996,11 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) // May be empty for this simple derivation std::vector refs; - auto refCb = LambdaAdapter{.fun = [&](const StorePath * refPath) { + auto refCb = LambdaAdapter{.fun = [&](const StorePath * refPath) -> nix_err { std::string name; nix_store_path_name(refPath, OBSERVE_STRING(name)); refs.push_back(name); + return NIX_OK; }}; ret = nix_path_info_get_references( ctx, info, static_cast(&refCb), decltype(refCb)::call_void); @@ -1007,7 +1008,10 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) ASSERT_EQ(ret, NIX_OK); std::vector sigs; - auto sigCb = LambdaAdapter{.fun = [&](const char * sig, unsigned int sig_len) { sigs.emplace_back(sig, sig_len); }}; + auto sigCb = LambdaAdapter{.fun = [&](const char * sig, unsigned int sig_len) -> nix_err { + sigs.emplace_back(sig, sig_len); + return NIX_OK; + }}; ret = nix_path_info_get_sigs( ctx, info, static_cast(&sigCb), decltype(sigCb)::call_void); assert_ctx_ok(); From 10964943a14b37125db76cc0c0b35477874ef6bd Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 15 Apr 2026 19:32:05 +0100 Subject: [PATCH 5/9] libstore-c: expand path info tests --- src/libstore-tests/nix_api_store.cc | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index 785a525a97c0..9a0a98bf832f 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -1053,4 +1053,106 @@ TEST_F(nix_api_store_test, nix_store_query_path_info_invalid_path) nix_store_path_free(path); } +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_references_early_exit) +{ + // Construct a path info with multiple references to test callback early exit + auto cppInfo = store->ptr->queryPathInfo(outPath->path); + auto mutInfo = std::make_shared(*cppInfo); + mutInfo->references.insert(outPath->path); + mutInfo->references.insert(drvPath->path); + auto * info = new nix_path_info{nix::ref(mutInfo)}; + + int callCount = 0; + auto refCb = LambdaAdapter{.fun = [&](const StorePath *) -> nix_err { + callCount++; + return NIX_ERR_UNKNOWN; + }}; + auto ret = nix_path_info_get_references( + ctx, info, static_cast(&refCb), decltype(refCb)::call_void); + + ASSERT_EQ(callCount, 1); + ASSERT_EQ(ret, NIX_ERR_UNKNOWN); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_sigs_early_exit) +{ + // Construct a path info with multiple signatures directly to avoid store cache issues + auto cppInfo = store->ptr->queryPathInfo(outPath->path); + auto mutInfo = std::make_shared(*cppInfo); + mutInfo->sigs.insert(nix::Signature::parse("key1:c2ln")); + mutInfo->sigs.insert(nix::Signature::parse("key2:c2ln")); + auto * info = new nix_path_info{nix::ref(mutInfo)}; + + int callCount = 0; + auto sigCb = LambdaAdapter{.fun = [&](const char *, unsigned int) -> nix_err { + callCount++; + return NIX_ERR_UNKNOWN; + }}; + auto ret = nix_path_info_get_sigs( + ctx, info, static_cast(&sigCb), decltype(sigCb)::call_void); + + ASSERT_EQ(callCount, 1); + ASSERT_EQ(ret, NIX_ERR_UNKNOWN); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver_absent) +{ + // The derivation itself was not built from another derivation, so deriver should be absent + nix_path_info * info = nix_store_query_path_info(ctx, store, drvPath); + assert_ctx_ok(); + ASSERT_NE(info, nullptr); + + StorePath * deriver = nix_path_info_get_deriver(ctx, info); + assert_ctx_ok(); + ASSERT_EQ(deriver, nullptr); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_ca_absent) +{ + // Construct a path info without a content address to test the absent-CA branch + auto cppInfo = store->ptr->queryPathInfo(outPath->path); + auto mutInfo = std::make_shared(*cppInfo); + mutInfo->ca = std::nullopt; + auto * info = new nix_path_info{nix::ref(mutInfo)}; + + bool callbackCalled = false; + auto ret = nix_path_info_get_ca( + ctx, info, [](const char *, unsigned int, void * ud) { *static_cast(ud) = true; }, &callbackCalled); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + ASSERT_FALSE(callbackCalled); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_null_callbacks) +{ + nix_path_info * info = nix_store_query_path_info(ctx, store, outPath); + assert_ctx_ok(); + ASSERT_NE(info, nullptr); + + // NULL callback for references should succeed without invoking anything + auto ret = nix_path_info_get_references(ctx, info, nullptr, nullptr); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + // NULL callback for sigs should succeed without invoking anything + ret = nix_path_info_get_sigs(ctx, info, nullptr, nullptr); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + // NULL callback for ca should succeed without invoking anything + ret = nix_path_info_get_ca(ctx, info, nullptr, nullptr); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + + nix_path_info_free(info); +} + } // namespace nixC From 41ab9d6cb77e28b1431d38ff3da3bcf505d9c043 Mon Sep 17 00:00:00 2001 From: Sander Date: Thu, 13 Aug 2026 11:54:41 +0200 Subject: [PATCH 6/9] libstore-c: improve callback error handling Pass context in the callback to match existing callback APIs. Update and improve tests. --- src/libstore-c/nix_api_store.cc | 16 +-- src/libstore-c/nix_api_store/path_info.h | 4 +- src/libstore-tests/nix_api_store.cc | 144 ++++++++++++++++------- 3 files changed, 112 insertions(+), 52 deletions(-) diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index 2a4cc3fe4c0c..b89ce6b627b5 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -424,7 +424,7 @@ nix_err nix_path_info_get_references( nix_c_context * context, const nix_path_info * path_info, void * user_data, - nix_err (*callback)(void * user_data, const StorePath * store_path)) + void (*callback)(nix_c_context * context, void * user_data, const StorePath * store_path)) { if (context) context->last_err_code = NIX_OK; @@ -432,9 +432,9 @@ nix_err nix_path_info_get_references( if (callback) { for (const auto & ref : path_info->info->references) { const StorePath tmp{ref}; - auto err = callback(user_data, &tmp); - if (err != NIX_OK) - return err; + callback(context, user_data, &tmp); + if (context && context->last_err_code != NIX_OK) + return context->last_err_code; } } } @@ -457,7 +457,7 @@ nix_err nix_path_info_get_sigs( nix_c_context * context, const nix_path_info * path_info, void * user_data, - nix_err (*callback)(void * user_data, const char * sig, unsigned int sig_len)) + void (*callback)(nix_c_context * context, void * user_data, const char * sig, unsigned int sig_len)) { if (context) context->last_err_code = NIX_OK; @@ -465,9 +465,9 @@ nix_err nix_path_info_get_sigs( if (callback) { for (const auto & sig : path_info->info->sigs) { auto s = sig.to_string(); - auto err = callback(user_data, s.data(), s.size()); - if (err != NIX_OK) - return err; + callback(context, user_data, s.data(), s.size()); + if (context && context->last_err_code != NIX_OK) + return context->last_err_code; } } } diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index 4159e3f0e418..01e5c2d10633 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -71,7 +71,7 @@ nix_err nix_path_info_get_references( nix_c_context * context, const nix_path_info * path_info, void * user_data, - nix_err (*callback)(void * user_data, const StorePath * store_path)); + void (*callback)(nix_c_context * context, void * user_data, const StorePath * store_path)); /** * @brief Get the deriver of a store path @@ -98,7 +98,7 @@ nix_err nix_path_info_get_sigs( nix_c_context * context, const nix_path_info * path_info, void * user_data, - nix_err (*callback)(void * user_data, const char * sig, unsigned int sig_len)); + void (*callback)(nix_c_context * context, void * user_data, const char * sig, unsigned int sig_len)); /** * @brief Get the content address of a store path, if any diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index 9a0a98bf832f..d81cfb8c559f 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -979,6 +979,7 @@ TEST_F(nix_api_store_test, nix_derivation_clone) TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) { + auto expected = store->ptr->queryPathInfo(outPath->path); nix_path_info * info = nix_store_query_path_info(ctx, store, outPath); assert_ctx_ok(); ASSERT_NE(info, nullptr); @@ -987,41 +988,50 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_store_query_path_info) auto ret = nix_path_info_get_nar_hash(ctx, info, OBSERVE_STRING(narHash)); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); - ASSERT_FALSE(narHash.empty()); - ASSERT_EQ(narHash.substr(0, 7), "sha256:"); + ASSERT_EQ(narHash, expected->narHash.to_string(nix::HashFormat::Nix32, true)); auto narSize = nix_path_info_get_nar_size(ctx, info); assert_ctx_ok(); - ASSERT_GT(narSize, 0u); - - // May be empty for this simple derivation - std::vector refs; - auto refCb = LambdaAdapter{.fun = [&](const StorePath * refPath) -> nix_err { - std::string name; - nix_store_path_name(refPath, OBSERVE_STRING(name)); - refs.push_back(name); - return NIX_OK; - }}; + ASSERT_EQ(narSize, expected->narSize); + + nix::StorePathSet refs; + + struct ReferenceCallbackData + { + nix::StorePathSet * refs; + }; + + ReferenceCallbackData refData{&refs}; ret = nix_path_info_get_references( - ctx, info, static_cast(&refCb), decltype(refCb)::call_void); + ctx, info, &refData, [](nix_c_context *, void * user_data, const StorePath * refPath) { + static_cast(user_data)->refs->insert(refPath->path); + }); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(refs, expected->references); - std::vector sigs; - auto sigCb = LambdaAdapter{.fun = [&](const char * sig, unsigned int sig_len) -> nix_err { - sigs.emplace_back(sig, sig_len); - return NIX_OK; - }}; + nix::Strings sigs; + + struct SignatureCallbackData + { + nix::Strings * sigs; + }; + + SignatureCallbackData sigData{&sigs}; ret = nix_path_info_get_sigs( - ctx, info, static_cast(&sigCb), decltype(sigCb)::call_void); + ctx, info, &sigData, [](nix_c_context *, void * user_data, const char * sig, unsigned int sig_len) { + static_cast(user_data)->sigs->emplace_back(sig, sig_len); + }); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(sigs, nix::Signature::toStrings(expected->sigs)); - // This is a CA derivation, so ca should be present + ASSERT_TRUE(expected->ca); std::string ca; ret = nix_path_info_get_ca(ctx, info, OBSERVE_STRING(ca)); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(ca, nix::renderContentAddress(*expected->ca)); nix_path_info_free(info); } @@ -1032,10 +1042,10 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver) assert_ctx_ok(); ASSERT_NE(info, nullptr); - // The output was built from a derivation, so deriver should be set StorePath * deriver = nix_path_info_get_deriver(ctx, info); assert_ctx_ok(); ASSERT_NE(deriver, nullptr); + ASSERT_EQ(deriver->path, drvPath->path); nix_store_path_free(deriver); nix_path_info_free(info); @@ -1055,53 +1065,107 @@ TEST_F(nix_api_store_test, nix_store_query_path_info_invalid_path) TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_references_early_exit) { - // Construct a path info with multiple references to test callback early exit auto cppInfo = store->ptr->queryPathInfo(outPath->path); auto mutInfo = std::make_shared(*cppInfo); mutInfo->references.insert(outPath->path); mutInfo->references.insert(drvPath->path); auto * info = new nix_path_info{nix::ref(mutInfo)}; - int callCount = 0; - auto refCb = LambdaAdapter{.fun = [&](const StorePath *) -> nix_err { - callCount++; - return NIX_ERR_UNKNOWN; - }}; + struct CallbackData + { + int callCount = 0; + }; + + CallbackData data; auto ret = nix_path_info_get_references( - ctx, info, static_cast(&refCb), decltype(refCb)::call_void); + ctx, info, &data, [](nix_c_context * context, void * user_data, const StorePath *) { + static_cast(user_data)->callCount++; + nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Test error from reference callback"); + }); - ASSERT_EQ(callCount, 1); + ASSERT_EQ(data.callCount, 1); ASSERT_EQ(ret, NIX_ERR_UNKNOWN); + ASSERT_EQ(nix_err_code(ctx), NIX_ERR_UNKNOWN); + ASSERT_STREQ(nix_err_msg(nullptr, ctx, nullptr), "Test error from reference callback"); nix_path_info_free(info); } TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_sigs_early_exit) { - // Construct a path info with multiple signatures directly to avoid store cache issues auto cppInfo = store->ptr->queryPathInfo(outPath->path); auto mutInfo = std::make_shared(*cppInfo); mutInfo->sigs.insert(nix::Signature::parse("key1:c2ln")); mutInfo->sigs.insert(nix::Signature::parse("key2:c2ln")); auto * info = new nix_path_info{nix::ref(mutInfo)}; - int callCount = 0; - auto sigCb = LambdaAdapter{.fun = [&](const char *, unsigned int) -> nix_err { - callCount++; - return NIX_ERR_UNKNOWN; - }}; + struct CallbackData + { + int callCount = 0; + }; + + CallbackData data; auto ret = nix_path_info_get_sigs( - ctx, info, static_cast(&sigCb), decltype(sigCb)::call_void); + ctx, info, &data, [](nix_c_context * context, void * user_data, const char *, unsigned int) { + static_cast(user_data)->callCount++; + nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Test error from signature callback"); + }); - ASSERT_EQ(callCount, 1); + ASSERT_EQ(data.callCount, 1); ASSERT_EQ(ret, NIX_ERR_UNKNOWN); + ASSERT_EQ(nix_err_code(ctx), NIX_ERR_UNKNOWN); + ASSERT_STREQ(nix_err_msg(nullptr, ctx, nullptr), "Test error from signature callback"); + + nix_path_info_free(info); +} + +TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_iterates_all_references_and_sigs) +{ + auto cppInfo = store->ptr->queryPathInfo(outPath->path); + auto mutInfo = std::make_shared(*cppInfo); + mutInfo->references.insert(outPath->path); + mutInfo->references.insert(drvPath->path); + mutInfo->sigs.insert(nix::Signature::parse("key1:c2ln")); + mutInfo->sigs.insert(nix::Signature::parse("key2:c2ln")); + auto * info = new nix_path_info{nix::ref(mutInfo)}; + + nix::StorePathSet refs; + + struct ReferenceCallbackData + { + nix::StorePathSet * refs; + }; + + ReferenceCallbackData refData{&refs}; + auto ret = nix_path_info_get_references( + ctx, info, &refData, [](nix_c_context *, void * user_data, const StorePath * refPath) { + static_cast(user_data)->refs->insert(refPath->path); + }); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(refs, mutInfo->references); + + nix::Strings sigs; + + struct SignatureCallbackData + { + nix::Strings * sigs; + }; + + SignatureCallbackData sigData{&sigs}; + ret = nix_path_info_get_sigs( + ctx, info, &sigData, [](nix_c_context *, void * user_data, const char * sig, unsigned int sig_len) { + static_cast(user_data)->sigs->emplace_back(sig, sig_len); + }); + assert_ctx_ok(); + ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(sigs, nix::Signature::toStrings(mutInfo->sigs)); nix_path_info_free(info); } TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver_absent) { - // The derivation itself was not built from another derivation, so deriver should be absent nix_path_info * info = nix_store_query_path_info(ctx, store, drvPath); assert_ctx_ok(); ASSERT_NE(info, nullptr); @@ -1115,7 +1179,6 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_deriver_absent) TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_ca_absent) { - // Construct a path info without a content address to test the absent-CA branch auto cppInfo = store->ptr->queryPathInfo(outPath->path); auto mutInfo = std::make_shared(*cppInfo); mutInfo->ca = std::nullopt; @@ -1137,17 +1200,14 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_null_callbacks) assert_ctx_ok(); ASSERT_NE(info, nullptr); - // NULL callback for references should succeed without invoking anything auto ret = nix_path_info_get_references(ctx, info, nullptr, nullptr); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); - // NULL callback for sigs should succeed without invoking anything ret = nix_path_info_get_sigs(ctx, info, nullptr, nullptr); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); - // NULL callback for ca should succeed without invoking anything ret = nix_path_info_get_ca(ctx, info, nullptr, nullptr); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); From 26ce3cbbe44815a6997256304fafbc21c3548c85 Mon Sep 17 00:00:00 2001 From: Sander Date: Thu, 13 Aug 2026 13:16:42 +0200 Subject: [PATCH 7/9] libstore-c: improve docs and add release note --- doc/manual/rl-next/c-api-path-info.md | 15 +++++++++++++++ src/libstore-c/nix_api_store/path_info.h | 7 ++++++- src/libutil-c/nix_api_util.h | 3 ++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 doc/manual/rl-next/c-api-path-info.md diff --git a/doc/manual/rl-next/c-api-path-info.md b/doc/manual/rl-next/c-api-path-info.md new file mode 100644 index 000000000000..e0df0fd1d1ff --- /dev/null +++ b/doc/manual/rl-next/c-api-path-info.md @@ -0,0 +1,15 @@ +--- +synopsis: "C API: Add store path metadata accessors" +prs: [15675] +--- + +The C API now includes functions for querying store path metadata: + +- `nix_store_query_path_info()` - Query metadata for a store path +- `nix_path_info_get_nar_hash()` - Get the NAR hash +- `nix_path_info_get_nar_size()` - Get the NAR size +- `nix_path_info_get_references()` - Iterate over references +- `nix_path_info_get_deriver()` - Get the deriver +- `nix_path_info_get_sigs()` - Iterate over signatures +- `nix_path_info_get_ca()` - Get the content address +- `nix_path_info_free()` - Free store path metadata diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index 01e5c2d10633..944b85f1ca74 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -3,7 +3,7 @@ /** * @defgroup libstore_pathinfo PathInfo * @ingroup libstore - * @brief Store path metadata (narHash, references, signatures, etc.) + * @brief Store path metadata * @{ */ /** @file @@ -60,6 +60,7 @@ uint64_t nix_path_info_get_nar_size(nix_c_context * context, const nix_path_info * * Calls the callback once for each reference. The StorePath passed to the * callback is borrowed and only valid for the duration of the callback. + * Iteration stops if the callback returns with `context` in an error state. * * @param[out] context Optional, stores error information * @param[in] path_info the nix_path_info to read from @@ -87,6 +88,10 @@ StorePath * nix_path_info_get_deriver(nix_c_context * context, const nix_path_in * @brief Iterate over the signatures of a store path * * Calls the callback once for each signature string (format: "keyName:base64sig"). + * The `sig` data is borrowed and the callback must not assume that the buffer + * persists after it returns. + * + * Iteration stops if the callback returns with `context` in an error state. * * @param[out] context Optional, stores error information * @param[in] path_info the nix_path_info to read from diff --git a/src/libutil-c/nix_api_util.h b/src/libutil-c/nix_api_util.h index b48d9166d4be..30862cb97f09 100644 --- a/src/libutil-c/nix_api_util.h +++ b/src/libutil-c/nix_api_util.h @@ -323,7 +323,8 @@ nix_err nix_err_code(const nix_c_context * read_context); /** * @brief Set an error message on a nix context. * - * This should be used when you want to throw an error from a PrimOp callback. + * Use this to report an error from a callback that receives a context, + * such as a PrimOp or iterator callback. * * All other use is internal to the API. * From b02f3e820c8afed862c0f4502c82304982352bd8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 2 Sep 2026 23:05:19 +0200 Subject: [PATCH 8/9] libstore-c: clarify NAR never empty --- src/libstore-c/nix_api_store/path_info.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index 944b85f1ca74..5dc90b4c242a 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -51,7 +51,8 @@ nix_err nix_path_info_get_nar_hash( * * @param[out] context Optional, stores error information * @param[in] path_info the nix_path_info to read from - * @return NAR size in bytes, 0 if unknown + * @return NAR size in bytes, 0 on error. Note that a NAR always has a root object, + * so an actual NAR stream is never empty. */ uint64_t nix_path_info_get_nar_size(nix_c_context * context, const nix_path_info * path_info); From 5a123e1484f3c7b02a61c7c1d685424f8f9b2382 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 3 Sep 2026 02:55:01 +0200 Subject: [PATCH 9/9] libstore-c: return distinct code NIX_ERR_KEY when get_ca not CA --- src/libstore-c/nix_api_store.cc | 4 +++- src/libstore-c/nix_api_store/path_info.h | 20 +++++++++++++++----- src/libstore-tests/nix_api_store.cc | 14 ++++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/libstore-c/nix_api_store.cc b/src/libstore-c/nix_api_store.cc index b89ce6b627b5..0d5870c729b3 100644 --- a/src/libstore-c/nix_api_store.cc +++ b/src/libstore-c/nix_api_store.cc @@ -480,7 +480,9 @@ nix_err nix_path_info_get_ca( if (context) context->last_err_code = NIX_OK; try { - if (path_info->info->ca && callback) { + if (!path_info->info->ca) + return nix_set_err_msg(context, NIX_ERR_KEY, "Store path is not content-addressed"); + if (callback) { auto res = renderContentAddress(*path_info->info->ca); return call_nix_get_string_callback(res, callback, user_data); } diff --git a/src/libstore-c/nix_api_store/path_info.h b/src/libstore-c/nix_api_store/path_info.h index 5dc90b4c242a..e94e88efc4d5 100644 --- a/src/libstore-c/nix_api_store/path_info.h +++ b/src/libstore-c/nix_api_store/path_info.h @@ -107,16 +107,26 @@ nix_err nix_path_info_get_sigs( void (*callback)(nix_c_context * context, void * user_data, const char * sig, unsigned int sig_len)); /** - * @brief Get the content address of a store path, if any + * @brief Get the content address of a store path, if it has one * - * If the path is content-addressed, calls the callback with the rendered - * content address string. If not content-addressed, the callback is not called. + * If so, "returns" the hash as a string with method and algorithm prefix in Nix base-32 encoding, + * e.g. `"fixed:r:sha256:1i89icvvs2f3cym00414i3bbl1qidhg0b5yrmdlx9cjkj5is6ljg"`. + * + * If the store object referenced by `path_info` is not content-addressed, + * the return code is `NIX_ERR_KEY`, and the callback is not called. + * + * `NIX_ERR_KEY` is only returned when `path_info` is not content-addressed. + * + * Input-addressed store paths have a content hash (see `nix_path_info_get_nar_hash`, + * but no content *address*, so that results in NIX_ERR_KEY, distinguishable from + * other, perhaps more unexpected errors. * * @param[out] context Optional, stores error information * @param[in] path_info the nix_path_info to read from - * @param[in] callback called with the content address string, if present + * @param[in] callback called with the content address string (only called when present) * @param[in] user_data arbitrary data, passed to the callback when it's called - * @return NIX_OK on success, error code on failure + * @return NIX_OK on success, NIX_ERR_KEY if the path is not content-addressed, + * another error code on failure */ nix_err nix_path_info_get_ca( nix_c_context * context, const nix_path_info * path_info, nix_get_string_callback callback, void * user_data); diff --git a/src/libstore-tests/nix_api_store.cc b/src/libstore-tests/nix_api_store.cc index d81cfb8c559f..de21fc00c5e9 100644 --- a/src/libstore-tests/nix_api_store.cc +++ b/src/libstore-tests/nix_api_store.cc @@ -1187,8 +1187,8 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_get_ca_absent) bool callbackCalled = false; auto ret = nix_path_info_get_ca( ctx, info, [](const char *, unsigned int, void * ud) { *static_cast(ud) = true; }, &callbackCalled); - assert_ctx_ok(); - ASSERT_EQ(ret, NIX_OK); + ASSERT_EQ(ret, NIX_ERR_KEY); + ASSERT_EQ(nix_err_code(ctx), NIX_ERR_KEY); ASSERT_FALSE(callbackCalled); nix_path_info_free(info); @@ -1208,10 +1208,20 @@ TEST_F(NixApiStoreTestWithRealisedPath, nix_path_info_null_callbacks) assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); + // outPath is content-addressed, so it succeeds, but lackign a callback, it + // ignores the value of it. ret = nix_path_info_get_ca(ctx, info, nullptr, nullptr); assert_ctx_ok(); ASSERT_EQ(ret, NIX_OK); + std::string ca; + ret = nix_path_info_get_ca(ctx, info, OBSERVE_STRING(ca)); + // The fixture provides a straightforward NAR-hashed output. + // Other CA paths may use a different method and prefix. + // Since the derivation has no impurities and a constant output, we can + // simply check the whole thing in one go: + ASSERT_EQ(ca, "fixed:r:sha256:1i89icvvs2f3cym00414i3bbl1qidhg0b5yrmdlx9cjkj5is6ljg"); + nix_path_info_free(info); }