diff --git a/local.mk b/local.mk index 7e3c77a65..f48eb63ee 100644 --- a/local.mk +++ b/local.mk @@ -2,7 +2,7 @@ GLOBAL_CXXFLAGS += -Wno-deprecated-declarations -Werror=switch # Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers. ERROR_SWITCH_ENUM = -Werror=switch-enum -$(foreach i, config.h $(wildcard src/lib*/*.hh), \ +$(foreach i, config.h $(wildcard src/lib*/*.hh) $(wildcard src/lib*/*.h), \ $(eval $(call install-file-in, $(i), $(includedir)/nix, 0644))) $(GCH): src/libutil/util.hh config.h diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 89f5ad021..5212805bf 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -137,6 +137,10 @@ public: : err(e) { } + std::string message() { + return err.msg.str(); + } + const char * what() const noexcept override { return calcWhat().c_str(); } const std::string & msg() const { return calcWhat(); } const ErrorInfo & info() const { calcWhat(); return err; } diff --git a/src/libutil/nix_api_util.cc b/src/libutil/nix_api_util.cc new file mode 100644 index 000000000..4f892637c --- /dev/null +++ b/src/libutil/nix_api_util.cc @@ -0,0 +1,148 @@ +#include "nix_api_util.h" +#include "config.hh" +#include "error.hh" +#include "nix_api_util_internal.h" +#include "util.hh" + +#include +#include + +nix_c_context *nix_c_context_create() { return new nix_c_context(); } + +void nix_c_context_free(nix_c_context *context) { delete context; } + +nix_err nix_context_error(nix_c_context *context) { + if (context == nullptr) { + throw; + } + try { + throw; + } catch (nix::Error &e) { + /* Storing this exception is annoying, take what we need here */ + context->last_err = e.what(); + context->info = e.info(); + int status; + const char *demangled = + abi::__cxa_demangle(typeid(e).name(), 0, 0, &status); + if (demangled) { + context->name = demangled; + // todo: free(demangled); + } else { + context->name = typeid(e).name(); + } + context->last_err_code = NIX_ERR_NIX_ERROR; + return context->last_err_code; + } catch (const std::exception &e) { + context->last_err = e.what(); + context->last_err_code = NIX_ERR_UNKNOWN; + return context->last_err_code; + } + // unreachable +} + +nix_err nix_set_err_msg(nix_c_context *context, nix_err err, const char *msg) { + if (context == nullptr) { + // todo last_err_code + throw new nix::Error("Nix C api error", msg); + } + context->last_err_code = err; + context->last_err = msg; + return err; +} + +const char *nix_version_get() { return PACKAGE_VERSION; } + +// Implementations +nix_err nix_setting_get(nix_c_context *context, const char *key, char *value, + int n) { + if (context) + context->last_err_code = NIX_OK; + try { + std::map settings; + nix::globalConfig.getSettings(settings); + if (settings.contains(key)) + return nix_export_std_string(context, settings[key].value, value, n); + else { + return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found"); + } + } + NIXC_CATCH_ERRS +} + +nix_err nix_setting_set(nix_c_context *context, const char *key, + const char *value) { + if (context) + context->last_err_code = NIX_OK; + if (nix::globalConfig.set(key, value)) + return NIX_OK; + else { + return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found"); + } +} + +nix_err nix_libutil_init(nix_c_context *context) { + if (context) + context->last_err_code = NIX_OK; + try { + nix::initLibUtil(); + return NIX_OK; + } + NIXC_CATCH_ERRS +} + +const char *nix_err_msg(nix_c_context *context, + const nix_c_context *read_context, unsigned int *n) { + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err) { + if (n) + *n = read_context->last_err->size(); + return read_context->last_err->c_str(); + } + nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message"); + return nullptr; +} + +nix_err nix_err_name(nix_c_context *context, const nix_c_context *read_context, + char *value, int n) { + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err_code != NIX_ERR_NIX_ERROR) { + return nix_set_err_msg(context, NIX_ERR_UNKNOWN, + "Last error was not a nix error"); + } + return nix_export_std_string(context, read_context->name, value, n); +} + +nix_err nix_err_info_msg(nix_c_context *context, + const nix_c_context *read_context, char *value, + int n) { + if (context) + context->last_err_code = NIX_OK; + if (read_context->last_err_code != NIX_ERR_NIX_ERROR) { + return nix_set_err_msg(context, NIX_ERR_UNKNOWN, + "Last error was not a nix error"); + } + return nix_export_std_string(context, read_context->info->msg.str(), value, + n); +} + +nix_err nix_err_code(nix_c_context *context, + const nix_c_context *read_context) { + if (context) + context->last_err_code = NIX_OK; + return read_context->last_err_code; +} + +// internal +nix_err nix_export_std_string(nix_c_context *context, + const std::string_view str, char *dest, + unsigned int n) { + size_t i = str.copy(dest, n - 1); + dest[i] = 0; + if (i == n - 1) { + return nix_set_err_msg(context, NIX_ERR_OVERFLOW, + "Provided buffer too short"); + } else + return NIX_OK; +} diff --git a/src/libutil/nix_api_util.h b/src/libutil/nix_api_util.h new file mode 100644 index 000000000..095564296 --- /dev/null +++ b/src/libutil/nix_api_util.h @@ -0,0 +1,223 @@ +#ifndef NIX_API_UTIL_H +#define NIX_API_UTIL_H + +/** @file + * @brief Main entry for the libutil C bindings + * + * Also contains error handling utilities + */ + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Error codes +/** + * @brief Type for error codes in the NIX system + * + * This type can have one of several predefined constants: + * - NIX_OK: No error occurred (0) + * - NIX_ERR_UNKNOWN: An unknown error occurred (-1) + * - NIX_ERR_OVERFLOW: An overflow error occurred (-2) + * - NIX_ERR_KEY: A key error occurred (-3) + * - NIX_ERR_NIX_ERROR: A generic Nix error occurred (-4) + */ +typedef int nix_err; + +/** + * @brief No error occurred. + * + * This error code is returned when no error has occurred during the function + * execution. + */ +#define NIX_OK 0 + +/** + * @brief An unknown error occurred. + * + * This error code is returned when an unknown error occurred during the + * function execution. + */ +#define NIX_ERR_UNKNOWN -1 + +/** + * @brief An overflow error occurred. + * + * This error code is returned when an overflow error occurred during the + * function execution. + */ +#define NIX_ERR_OVERFLOW -2 + +/** + * @brief A key error occurred. + * + * This error code is returned when a key error occurred during the function + * execution. + */ +#define NIX_ERR_KEY -3 + +/** + * @brief A generic Nix error occurred. + * + * This error code is returned when a generic Nix error occurred during the + * function execution. + */ +#define NIX_ERR_NIX_ERROR -4 + +/** + * @brief This object stores error state. + * + * Passed as a first parameter to C functions that can fail, will store error + * information. Optional wherever it is used, passing NULL will throw a C++ + * exception instead. The first field is a nix_err, that can be read directly to + * check for errors. + * @note These can be reused between different function calls, + * but make sure not to use them for multiple calls simultaneously (which can + * happen in callbacks). + */ +typedef struct nix_c_context nix_c_context; + +// Function prototypes + +/** + * @brief Allocate a new nix_c_context. + * @throws std::bad_alloc + * @return allocated nix_c_context, owned by the caller. Free using + * `nix_c_context_free`. + */ +nix_c_context *nix_c_context_create(); +/** + * @brief Free a nix_c_context. Does not fail. + * @param[out] context The context to free, mandatory. + */ +void nix_c_context_free(nix_c_context *context); + +/** + * @brief Initializes nix_libutil and its dependencies. + * + * This function can be called multiple times, but should be called at least + * once prior to any other nix function. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization is successful, or an error code + * otherwise. + */ +nix_err nix_libutil_init(nix_c_context *context); + +/** + * @brief Retrieves a setting from the nix global configuration. + * + * This function requires nix_libutil_init() to be called at least once prior to + * its use. + * + * @param[out] context optional, Stores error information + * @param[in] key The key of the setting to retrieve. + * @param[out] value A pointer to a buffer where the value of the setting will + * be stored. + * @param[in] n The size of the buffer pointed to by value. + * @return NIX_ERR_KEY if the setting is unknown, NIX_ERR_OVERFLOW if the + * provided buffer is too short, or NIX_OK if the setting was retrieved + * successfully. + */ +nix_err nix_setting_get(nix_c_context *context, const char *key, char *value, + int n); + +/** + * @brief Sets a setting in the nix global configuration. + * + * Use "extra-" to append to the setting's value. + * + * Settings only apply for new States. Call nix_plugins_init() when you are done + * with the settings to load any plugins. + * + * @param[out] context optional, Stores error information + * @param[in] key The key of the setting to set. + * @param[in] value The value to set for the setting. + * @return NIX_ERR_KEY if the setting is unknown, or NIX_OK if the setting was + * set successfully. + */ +nix_err nix_setting_set(nix_c_context *context, const char *key, + const char *value); + +// todo: nix_plugins_init() + +/** + * @brief Retrieves the nix library version. + * + * Does not fail. + * @return A static string representing the version of the nix library. + */ +const char *nix_version_get(); + +/** + * @brief Retrieves the most recent error message from a context. + * + * @pre This function should only be called after a previous nix function has + * returned an error. + * + * @param[out] context optional, the context to store errors in if this function + * fails + * @param[in] ctx the context to retrieve the error message from + * @param[out] n optional: a pointer to an unsigned int that is set to the + * length of the error. + * @return nullptr if no error message was ever set, + * a borrowed pointer to the error message otherwise. + */ +const char *nix_err_msg(nix_c_context *context, const nix_c_context *ctx, + unsigned int *n); + +/** + * @brief Retrieves the error message from errorInfo in a context. + * + * Used to inspect nix Error messages. + * + * @pre This function should only be called after a previous nix function has + * returned a NIX_ERR_NIX_ERROR + * + * @param[out] context optional, the context to store errors in if this function + * fails + * @param[in] read_context the context to retrieve the error message from + * @param[out] value The allocated area to write the error string to. + * @param[in] n Maximum size of the returned string. + * @return NIX_OK if there were no errors, an error code otherwise. + */ +nix_err nix_err_info_msg(nix_c_context *context, + const nix_c_context *read_context, char *value, int n); + +/** + * @brief Retrieves the error name from a context. + * + * Used to inspect nix Error messages. + * + * @pre This function should only be called after a previous nix function has + * returned a NIX_ERR_NIX_ERROR + * + * @param context optional, the context to store errors in if this function + * fails + * @param[in] read_context the context to retrieve the error message from + * @param[out] value The allocated area to write the error string to. + * @param[in] n Maximum size of the returned string. + * @return NIX_OK if there were no errors, an error code otherwise. + */ +nix_err nix_err_name(nix_c_context *context, const nix_c_context *read_context, + char *value, int n); + +/** + * @brief Retrieves the most recent error code from a nix_c_context + * + * Equivalent to reading the first field of the context. + * + * @param[out] context optional, the context to store errors in if this function + * fails + * @param[in] read_context the context to retrieve the error message from + * @return most recent error code stored in the context. + */ +nix_err nix_err_code(nix_c_context *context, const nix_c_context *read_context); + +// cffi end +#ifdef __cplusplus +} +#endif + +#endif // NIX_API_UTIL_H diff --git a/src/libutil/nix_api_util_internal.h b/src/libutil/nix_api_util_internal.h new file mode 100644 index 000000000..9ece28588 --- /dev/null +++ b/src/libutil/nix_api_util_internal.h @@ -0,0 +1,61 @@ +#ifndef NIX_API_UTIL_INTERNAL_H +#define NIX_API_UTIL_INTERNAL_H + +#include + +// forward declaration +namespace nix { +class Error; +}; + +struct nix_c_context { + nix_err last_err_code = NIX_OK; + std::optional last_err = {}; + std::optional info = {}; + std::string name = ""; +}; + +nix_err nix_context_error(nix_c_context *context); + +/** + * Internal use only. + * + * Sets the most recent error message. + * + * @param context context to write the error message to, or NULL + * @param err The error code to set and return + * @param msg The error message to set. + * @returns the error code set + */ +nix_err nix_set_err_msg(nix_c_context *context, nix_err err, const char *msg); + +/** + * Internal use only. + * + * Export a std::string across the C api boundary + * @param context optional, the context to store errors in if this function + * fails + * @param str The string to export + * @param value The allocated area to write the string to. + * @param n Maximum size of the returned string. + * @return NIX_OK if there were no errors, NIX_ERR_OVERFLOW if the string length + * exceeds `n`. + */ +nix_err nix_export_std_string(nix_c_context *context, + const std::string_view str, char *dest, + unsigned int n); + +#define NIXC_CATCH_ERRS \ + catch (...) { \ + return nix_context_error(context); \ + } \ + return NIX_OK; + +#define NIXC_CATCH_ERRS_RES(def) \ + catch (...) { \ + nix_context_error(context); \ + return def; \ + } +#define NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_RES(nullptr) + +#endif // NIX_API_UTIL_INTERNAL_H