diff --git a/CMakeLists.txt b/CMakeLists.txt index 4972e19..ad8ea62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.14) project(easylibstorage C) set(CMAKE_C_STANDARD 11) -if (NOT DEFINED LOGOS_STORAGE_NIM_ROOT) - message(FATAL_ERROR "Need to set LOGOS_STORAGE_NIM_ROOT") +if (NOT LOGOS_STORAGE_NIM_ROOT) + message(FATAL_ERROR "libstorage repo not found. Set LOGOS_STORAGE_NIM_ROOT to the folder containing the libstorage repo before running cmake.") endif () # --- Find libstorage --- @@ -18,11 +18,15 @@ endif () find_library(LIBSTORAGE_PATH NAMES ${LIBSTORAGE_NAMES} PATHS ${LOGOS_STORAGE_NIM_ROOT}/build NO_DEFAULT_PATH) if (NOT LIBSTORAGE_PATH) - message(WARNING "libstorage not found. Build or provide it before linking.") + message(FATAL_ERROR "could not find ${LIBSTORAGE_NAMES} under ${LIBSTORAGE_DIR}/build. Make sure to build it before running cmake.") endif () +# --- Vendored: inih --- +add_library(inih STATIC vendor/inih/ini.c) +target_include_directories(inih PUBLIC vendor/inih) + # --- Shared library: easystorage --- -add_library(easystorage SHARED +add_library(easystorage STATIC easystorage.c easystorage.h ) @@ -32,9 +36,7 @@ target_include_directories(easystorage PUBLIC "${LOGOS_STORAGE_NIM_ROOT}/library" ) -if (LIBSTORAGE_PATH) - target_link_libraries(easystorage PRIVATE ${LIBSTORAGE_PATH}) -endif () +target_link_libraries(easystorage PRIVATE ${LIBSTORAGE_PATH} inih) # --- Example: storageconsole --- add_executable(storageconsole @@ -42,10 +44,7 @@ add_executable(storageconsole ) target_link_libraries(storageconsole PRIVATE easystorage) - -if (LIBSTORAGE_PATH) - target_link_libraries(storageconsole PRIVATE ${LIBSTORAGE_PATH}) -endif () +target_link_libraries(storageconsole PRIVATE ${LIBSTORAGE_PATH}) # --- Example: uploader/downloader --- add_executable(uploader @@ -56,11 +55,8 @@ add_executable(downloader target_link_libraries(uploader PRIVATE easystorage) target_link_libraries(downloader PRIVATE easystorage) - -if (LIBSTORAGE_PATH) - target_link_libraries(uploader PRIVATE ${LIBSTORAGE_PATH}) - target_link_libraries(downloader PRIVATE ${LIBSTORAGE_PATH}) -endif () +target_link_libraries(uploader PRIVATE ${LIBSTORAGE_PATH}) +target_link_libraries(downloader PRIVATE ${LIBSTORAGE_PATH}) # --- Tests --- enable_testing() @@ -71,9 +67,13 @@ add_executable(test_runner tests/mock_libstorage.c ) +target_link_libraries(test_runner PRIVATE inih) + target_include_directories(test_runner PRIVATE "${CMAKE_SOURCE_DIR}" "${LOGOS_STORAGE_NIM_ROOT}/library" ) +target_link_libraries(test_runner PRIVATE inih) + add_test(NAME easystorage_tests COMMAND test_runner) diff --git a/README.md b/README.md index c5c444a..1b25aae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # libeasystorage -A simplified, higher level C wrapper around [libstorage](https://github.com/status-im/logos-storage-nim) built to showcase it. Allows filesharing in the Logos Storage network. Comes with an example console application to interact with it. +A simplified, higher level C wrapper around [libstorage](https://github.com/status-im/logos-storage-nim). Includes +examples showing how to implement simple filesharing apps. ## Prerequisites diff --git a/easystorage.c b/easystorage.c index 8845dd0..b99931b 100644 --- a/easystorage.c +++ b/easystorage.c @@ -1,4 +1,5 @@ #include "easystorage.h" +#include "ini.h" #include "libstorage.h" #include @@ -12,6 +13,13 @@ #define POLL_INTERVAL_US (100 * 1000) #define DEFAULT_CHUNK_SIZE (64 * 1024) +const node_config DEFAULT_STORAGE_NODE_CONFIG = {.api_port = 8080, + .disc_port = 8090, + .data_dir = "./data", + .log_level = "INFO", + .bootstrap_node = NULL, + .nat = "auto"}; + typedef struct { int ret; char *msg; @@ -30,8 +38,10 @@ static resp *resp_alloc(void) { } static void resp_destroy(resp *r) { - if (!r) return; - if (r->msg) free(r->msg); + if (!r) + return; + if (r->msg) + free(r->msg); free(r); } @@ -44,7 +54,8 @@ static void resp_wait(resp *r) { // Callback for simple (non-progress) async operations. static void on_complete(int ret, const char *msg, size_t len, void *userData) { resp *r = userData; - if (!r) return; + if (!r) + return; pthread_mutex_lock(&mutex); if (r->unreferenced) { @@ -70,10 +81,10 @@ static void on_complete(int ret, const char *msg, size_t len, void *userData) { // Callback for operations that report progress before completing. static void on_progress(int ret, const char *msg, size_t len, void *userData) { resp *r = userData; - if (!r) return; + if (!r) + return; pthread_mutex_lock(&mutex); - if (r->unreferenced) { resp_destroy(r); pthread_mutex_unlock(&mutex); @@ -282,3 +293,50 @@ int e_storage_download(STORAGE_NODE node, const char *cid, const char *filepath, return ret; } + +static int handler(void *user, const char *section, const char *name, const char *value) { + node_config *cfg = (node_config *) user; +#define MATCH(n) strcmp(section, "easystorage") == 0 && strcmp(name, n) == 0 + if (MATCH("bootstrap-node")) { + cfg->bootstrap_node = strdup(value); + } else if (MATCH("data-dir")) { + cfg->data_dir = strdup(value); + } else if (MATCH("log-level")) { + cfg->log_level = strdup(value); + } else if (MATCH("nat")) { + cfg->nat = strdup(value); + } else if (MATCH("api-port")) { + cfg->api_port = atoi(value); + } else if (MATCH("disc-port")) { + cfg->disc_port = atoi(value); + } else { + return RET_OK; + } + + return RET_ERR; +} + +int e_storage_read_config(char *filepath, node_config *conf) { return ini_parse(filepath, handler, conf); } +int e_storage_read_config_file(FILE *fp, node_config *config) { return ini_parse_file(fp, handler, config); } + +void e_storage_free_config(node_config *conf) { + if (!conf) { + return; + } + if (conf->bootstrap_node) { + free(conf->bootstrap_node); + conf->bootstrap_node = NULL; + } + if (conf->data_dir) { + free(conf->data_dir); + conf->data_dir = NULL; + } + if (conf->log_level) { + free(conf->log_level); + conf->log_level = NULL; + } + if (conf->nat) { + free(conf->nat); + conf->nat = NULL; + } +} diff --git a/easystorage.h b/easystorage.h index da2ce8d..36a8c22 100644 --- a/easystorage.h +++ b/easystorage.h @@ -1,6 +1,8 @@ #ifndef EASYSTORAGE_H #define EASYSTORAGE_H +#include + #define STORAGE_NODE void * typedef struct { @@ -12,6 +14,8 @@ typedef struct { char *nat; } node_config; +extern const node_config DEFAULT_STORAGE_NODE_CONFIG; + typedef void (*progress_callback)(int total, int complete, int status); // Creates a new storage node. Returns opaque pointer, or NULL on failure. @@ -30,4 +34,10 @@ char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callbac // Downloads content identified by cid to filepath. Returns 0 on success. int e_storage_download(STORAGE_NODE node, const char *cid, const char *filepath, progress_callback cb); +// Config handling utilities. Note that for e_storage_read_config and e_storage_read_config, the +// caller is responsible for freeing the config object and its members. +int e_storage_read_config(char *filepath, node_config *config); +int e_storage_read_config_file(FILE *, node_config *config); +void e_storage_free_config(node_config *config); + #endif // EASYSTORAGE_H diff --git a/examples/downloader.c b/examples/downloader.c index 6455110..5107154 100644 --- a/examples/downloader.c +++ b/examples/downloader.c @@ -1,3 +1,5 @@ +/* downloader.c: Download files from a Logos Storage node into the local disk. + */ #include #include #include "easystorage.h" @@ -9,22 +11,26 @@ void progress(int total, int complete, int status) { int main(int argc, char *argv[]) { if (argc < 4) { - printf("Usage: %s \n", argv[0]); + printf("Usage: %s BOOTSTRAP_SPR CID \n", argv[0]); exit(1); } + char *spr = argv[1]; + char *cid = argv[2]; + char *filepath = argv[3]; + node_config cfg = { .api_port = 8081, .disc_port = 9091, .data_dir = "./downloader-data", .log_level = "INFO", - .bootstrap_node = argv[1], + .bootstrap_node = spr, .nat = "none", }; STORAGE_NODE node = e_storage_new(cfg); e_storage_start(node); - e_storage_download(node, argv[2], argv[3], progress); + e_storage_download(node, cid, filepath, progress); e_storage_stop(node); e_storage_destroy(node); } diff --git a/examples/uploader.c b/examples/uploader.c index 4ad31d5..8b8eb1c 100644 --- a/examples/uploader.c +++ b/examples/uploader.c @@ -1,3 +1,5 @@ +/* uploader.c: makes a local file available to the Logos Storage network. + */ #include #include #include "easystorage.h" @@ -22,10 +24,12 @@ int main(int argc, char *argv[]) { .nat = "none", }; + char *filepath = argv[1]; + STORAGE_NODE node = e_storage_new(cfg); e_storage_start(node); - char *cid = e_storage_upload(node, argv[1], progress); + char *cid = e_storage_upload(node, filepath, progress); char *spr = e_storage_spr(node); printf("Run: downloader %s %s ./output-file\n", spr, cid); diff --git a/tests/test_runner.c b/tests/test_runner.c index d3677e3..3753f24 100644 --- a/tests/test_runner.c +++ b/tests/test_runner.c @@ -30,6 +30,14 @@ static node_config default_config(void) { return cfg; } +FILE *write_to_temp(const char *contents) { + FILE *fp = tmpfile(); + assert(fp != NULL); + assert(fwrite(contents, 1, strlen(contents), fp) == strlen(contents)); + assert(fseek(fp, 0, SEEK_SET) == 0); + return fp; +} + // --- Tests --- static void test_new(void) { @@ -147,6 +155,30 @@ static void test_full_lifecycle(void) { assert(e_storage_destroy(node) == RET_OK); } +static void test_should_read_configuration_file(void) { + const char *conf = "[easystorage] \n" + "bootstrap-node=spr:CiUIAhIhA-VlcoiRm02KyIzrcTP \n" + "data-dir=/home/user/data-dir \n" + "log-level=WARN \n" + "api-port=8081 \n" + "disc-port=8091 \n" + "nat=none \n"; + + node_config cfg = DEFAULT_STORAGE_NODE_CONFIG; + FILE *cfg_file = write_to_temp(conf); + assert(e_storage_read_config_file(cfg_file, &cfg) == RET_OK); + fclose(cfg_file); + + assert(strcmp(cfg.bootstrap_node, "spr:CiUIAhIhA-VlcoiRm02KyIzrcTP") == 0); + assert(strcmp(cfg.data_dir, "/home/user/data-dir") == 0); + assert(strcmp(cfg.log_level, "WARN") == 0); + assert(cfg.api_port == 8081); + assert(cfg.disc_port == 8091); + assert(strcmp(cfg.nat, "none") == 0); + + e_storage_free_config(&cfg); +} + int main(void) { printf("Running easylibstorage tests...\n"); @@ -163,7 +195,8 @@ int main(void) { RUN_TEST(test_download_null); RUN_TEST(test_get_should_get_node_spr); RUN_TEST(test_full_lifecycle); + RUN_TEST(test_should_read_configuration_file); printf("\n%d/%d tests passed.\n", tests_passed, tests_run); - return (tests_passed == tests_run) ? 0 : 1; + return tests_passed == tests_run ? 0 : 1; } diff --git a/vendor/inih/LICENSE.txt b/vendor/inih/LICENSE.txt new file mode 100644 index 0000000..cb7ee2d --- /dev/null +++ b/vendor/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/inih/ini.c b/vendor/inih/ini.c new file mode 100644 index 0000000..6f0919d --- /dev/null +++ b/vendor/inih/ini.c @@ -0,0 +1,333 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2025, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. end must be a + pointer to the NUL terminator at the end of the string. Return s. */ +static char* ini_rstrip(char* s, char* end) +{ + while (end > s && isspace((unsigned char)(*--end))) + *end = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* ini_lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* ini_find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* ini_strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + size_t max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; +#endif + char section[MAX_SECTION] = ""; +#if INI_ALLOW_MULTILINE + char prev_name[MAX_NAME] = ""; +#endif + + size_t offset; + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + char abyss[16]; /* Used to consume input when a line is too long. */ + size_t abyss_len; + + assert(reader != NULL); + assert(stream != NULL); + assert(handler != NULL); + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { + offset = strlen(line); + +#if INI_ALLOW_REALLOC && !INI_USE_STACK + while (max_line < INI_MAX_LINE && + offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */ + if (offset == max_line - 1 && line[offset - 1] != '\n') { + while (reader(abyss, sizeof(abyss), stream) != NULL) { + if (!error) + error = lineno; + abyss_len = strlen(abyss); + if (abyss_len > 0 && abyss[abyss_len - 1] == '\n') + break; + } + } + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = ini_rstrip(ini_lskip(start), line + offset); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { +#if INI_ALLOW_INLINE_COMMENTS + end = ini_find_chars_or_comment(start, NULL); + *end = '\0'; + ini_rstrip(start, end); +#endif + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = ini_find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + ini_strncpy0(section, start + 1, sizeof(section)); +#if INI_ALLOW_MULTILINE + *prev_name = '\0'; +#endif +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = ini_find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = ini_rstrip(start, end); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = ini_find_chars_or_comment(value, NULL); + *end = '\0'; +#endif + value = ini_lskip(value); + ini_rstrip(value, end); + +#if INI_ALLOW_MULTILINE + ini_strncpy0(prev_name, name, sizeof(prev_name)); +#endif + /* Valid name[=:]value pair found, call handler */ + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = ini_rstrip(start, end); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + if (!error) + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + return ini_parse_string_length(string, strlen(string), handler, user); +} + +/* See documentation in header file. */ +int ini_parse_string_length(const char* string, size_t length, + ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = length; + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/vendor/inih/ini.h b/vendor/inih/ini.h new file mode 100644 index 0000000..07aa7f4 --- /dev/null +++ b/vendor/inih/ini.h @@ -0,0 +1,189 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2025, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Visibility symbols, required for Windows DLLs */ +#ifndef INI_API +#if defined _WIN32 || defined __CYGWIN__ +# ifdef INI_SHARED_LIB +# ifdef INI_SHARED_LIB_BUILDING +# define INI_API __declspec(dllexport) +# else +# define INI_API __declspec(dllimport) +# endif +# else +# define INI_API +# endif +#else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define INI_API __attribute__ ((visibility ("default"))) +# else +# define INI_API +# endif +#endif +#endif + +/* Typedef for prototype of handler function. + + Note that even though the value parameter has type "const char*", the user + may cast to "char*" and modify its content, as the value is not used again + after the call to ini_handler. This is not true of section and name -- + those must not be modified. +*/ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +INI_API int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data + instead of a file. Useful for parsing INI data from a network socket or + which is already in memory. */ +INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Same as ini_parse_string(), but takes a string and its length, avoiding + strlen(). Useful for parsing INI data from a network socket or which is + already in memory, or interfacing with C++ std::string_view. */ +INI_API int ini_parse_string_length(const char* string, size_t length, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */