diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index 1e0f5e6..0000000 --- a/.clang-tidy +++ /dev/null @@ -1,2 +0,0 @@ ---- -Checks: 'clang-diagnostics-*,clang-analyzer-*,linuxkernel-*,modernize-*,readability-*,-readability-magic-numbers,portability-*' diff --git a/.drone.yml b/.drone.yml index 3174f1e..ce40462 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,12 +29,12 @@ steps: image: serguzim/emgauwa-builder pull: always commands: - - tar xzf emgauwa-controller.tar.gz - - cd controller - - mkdir build - - cd build - - cmake -DWIRING_PI_DEBUG=on .. - - make + - tar xzf emgauwa-controller.tar.gz + - cd controller + - mkdir build + - cd build + - cmake -DWIRING_PI_DEBUG=on .. + - make - name: test image: serguzim/emgauwa-builder @@ -53,8 +53,9 @@ steps: api_key: from_secret: gitea_token base_url: https://git.serguzim.me - title: ${DRONE_TAG} - + files: + - build/core + - build/core.ini when: event: tag diff --git a/.editorconfig b/.editorconfig index 146a218..ab2168d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,3 @@ end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 - -[*.yml] -indent_style = space -indent_size = 2 diff --git a/.envrc b/.envrc deleted file mode 100644 index 1d953f4..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use nix diff --git a/.gitignore b/.gitignore index aae5f18..47894a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,7 @@ build/ docs/ -tests/testing/ +tests/testing_latest/ +tests/testing_bak/ include/sql/*.h - -emgauwa-core.conf.d - -emgauwa-core.sqlite -vgcore.* diff --git a/CMakeLists.txt b/CMakeLists.txt index 39a60be..a763453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.7) project(core - VERSION 0.4.5 + VERSION 0.3.4 LANGUAGES C) add_executable(core src/main.c) @@ -8,18 +8,11 @@ add_executable(core src/main.c) target_link_libraries(core -lsqlite3) target_link_libraries(core -luuid) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra") +set(CMAKE_C_FLAGS "$ENV{CFLAGS}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra -ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - - -file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c) - - add_definitions("-DMG_ENABLE_EXTRA_ERRORS_DESC -DMG_ENABLE_MQTT_BROKER") aux_source_directory(src/ SRC_DIR) @@ -31,6 +24,7 @@ aux_source_directory(vendor VENDOR_SRC) add_dependencies(core sql) +configure_file("core.ini" "core.ini" COPYONLY) configure_file("version.h.in" "version.h" @ONLY) @@ -45,8 +39,27 @@ add_custom_target(sql ) add_custom_target(run - COMMAND ${CMAKE_BINARY_DIR}/core + COMMAND ./core start DEPENDS core + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_custom_target(debug + COMMAND valgrind -s ./core start + DEPENDS core + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_custom_target(debug-leak + COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core start + DEPENDS core + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_custom_target(debug-callgrind + COMMAND valgrind --tool=callgrind ./core start + DEPENDS core + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_custom_target(docs + COMMAND doxygen WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) @@ -55,57 +68,13 @@ add_custom_target(test DEPENDS core WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests ) - -add_custom_target(docs - COMMAND doxygen +add_custom_target(test-callgrind + COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "--tool=callgrind" + DEPENDS core + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests +) +add_custom_target(coverage + COMMAND gcovr -s --root ${CMAKE_SOURCE_DIR} -e ${CMAKE_SOURCE_DIR}/vendor --html-details ${CMAKE_BINARY_DIR}/coverage.html --html-title "Emgauwa Core Coverage" ${CMAKE_BINARY_DIR}/CMakeFiles/core.dir + DEPENDS test WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) - -IF(CMAKE_BUILD_TYPE MATCHES Debug) - message(STATUS "loading debug targets") - add_custom_target(debug - COMMAND gdb ${CMAKE_BINARY_DIR}/core - DEPENDS core - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - add_custom_target(valgrind - COMMAND valgrind -s ${CMAKE_BINARY_DIR}/core - DEPENDS core - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - add_custom_target(valgrind-leak - COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/core - DEPENDS core - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - add_custom_target(valgrind-callgrind - COMMAND valgrind --tool=callgrind ${CMAKE_BINARY_DIR}/core - DEPENDS core - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - add_custom_target(coverage - COMMAND gcovr -s --root ${CMAKE_SOURCE_DIR} -e ${CMAKE_SOURCE_DIR}/vendor --html-details ${CMAKE_BINARY_DIR}/coverage.html --html-title "Emgauwa Core Coverage" ${CMAKE_BINARY_DIR}/CMakeFiles/core.dir - DEPENDS test - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - - add_custom_target(test-callgrind - COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "--tool=callgrind" - DEPENDS core - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - ) - - add_custom_target( - clang-tidy - COMMAND /usr/bin/clang-tidy - ${ALL_SOURCE_FILES} - --header-filter=${CMAKE_SOURCE_DIR}/include/* - -- - -std=gnu99 - -I${CMAKE_SOURCE_DIR}/include - -I${CMAKE_SOURCE_DIR}/vendor - -I${CMAKE_BINARY_DIR} - -DMG_ENABLE_EXTRA_ERRORS_DESC - -DMG_ENABLE_MQTT_BROKER - ) -ENDIF(CMAKE_BUILD_TYPE MATCHES Debug) diff --git a/Doxyfile b/Doxyfile index 0f2ab0b..a186f7e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.20 +# Doxyfile 1.8.17 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -227,14 +227,6 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -271,6 +263,12 @@ TAB_SIZE = 4 ALIASES = +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -312,13 +310,13 @@ OPTIMIZE_OUTPUT_SLICE = NO # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # @@ -457,19 +455,6 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -574,7 +559,7 @@ INTERNAL_DOCS = NO # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) and Mac users are advised to set this option to NO. +# (including Cygwin) ands Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -844,8 +829,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = src/ \ - include/ +INPUT = . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -869,7 +853,7 @@ INPUT_ENCODING = UTF-8 # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, # *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ @@ -1394,7 +1378,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). +# (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1530,7 +1514,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = YES +GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -1556,17 +1540,6 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1622,7 +1595,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ @@ -1861,11 +1834,9 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as -# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX -# files. Set this option to YES, to get a higher quality PDF documentation. -# -# See also section LATEX_CMD_NAME for selecting the engine. +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a +# higher quality PDF documentation. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2104,10 +2075,6 @@ DOCBOOK_PROGRAMLISTING = NO GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# Configuration options related to Sqlite3 output -#--------------------------------------------------------------------------- - #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2281,7 +2248,7 @@ EXTERNAL_PAGES = YES # powerful graphs. # The default value is: YES. -CLASS_DIAGRAMS = NO +CLASS_DIAGRAMS = YES # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The @@ -2303,7 +2270,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = YES +HAVE_DOT = NO # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2420,7 +2387,7 @@ INCLUDED_BY_GRAPH = YES # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -CALL_GRAPH = YES +CALL_GRAPH = NO # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller # dependency graph for every global function or class method. @@ -2432,7 +2399,7 @@ CALL_GRAPH = YES # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -CALLER_GRAPH = YES +CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical # hierarchy of all classes instead of a textual one. diff --git a/core.ini b/core.ini new file mode 100644 index 0000000..cd40564 --- /dev/null +++ b/core.ini @@ -0,0 +1,16 @@ +[core] +server-port = 5000 +database = core.sqlite +content-dir = /usr/share/webapps/emgauwa +not-found-file = 404.html +not-found-file-mime = text/html +not-found-content = 404 - NOT FOUND +not-found-content-type = text/plain + +: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env +discovery-port = 4421 +: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env +mqtt-port = 1885 + +log-level = debug +log-file = stdout diff --git a/emgauwa-core.conf b/emgauwa-core.conf deleted file mode 100644 index d799d82..0000000 --- a/emgauwa-core.conf +++ /dev/null @@ -1,19 +0,0 @@ -[core] -database = "emgauwa-core.sqlite" -content-dir = "/usr/share/webapps/emgauwa" -#include = "./emgauwa-core.conf.d/" -not-found-file = "404.html" -not-found-file-mime = "text/html" -not-found-content = "404 - NOT FOUND" -not-found-content-type = "text/plain" - -[ports] -server = 4419 -# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env -discovery = 4421 -# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env -mqtt = 1885 - -[logging] -level = "debug" -file = "stdout" diff --git a/include/cache.h b/include/cache.h index 3e2ff7c..8fde368 100644 --- a/include/cache.h +++ b/include/cache.h @@ -36,6 +36,7 @@ void cache_invalidate_relay(int relay_id, int status_relay); + void cache_put_json_controller(int controller_id, char *controller_json); @@ -46,16 +47,6 @@ void cache_invalidate_controller(int controller_id); -void -cache_put_json_macro(int macro_id, char *macro_json); - -char* -cache_get_json_macro(int macro_id); - -void -cache_invalidate_macro(int macro_id); - - void cache_invalidate_tagged(int tag_id); diff --git a/include/cli.h b/include/cli.h deleted file mode 100644 index cf3e2b1..0000000 --- a/include/cli.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef CORE_CLI_H -#define CORE_CLI_H - -typedef struct cli -{ - const char *config_file; -} cli_t; - -void -cli_parse(int argc, const char **argv, cli_t *cli); - -#endif /* CORE_CLI_H */ diff --git a/include/config.h b/include/config.h index ca9fbff..49c04af 100644 --- a/include/config.h +++ b/include/config.h @@ -4,54 +4,37 @@ #include <stdint.h> #include <mongoose.h> -#include <toml.h> +#include <confini.h> + +typedef enum +{ + RUN_TYPE_START, + RUN_TYPE_INVALID, +} run_type_t; typedef struct { - char *include; - char *database; - char *user; - char *group; - char *content_dir; - char *not_found_file; - char *not_found_file_type; - char *not_found_content; - char *not_found_content_type; - - struct - { - int level; - FILE *file; - } logging; - - struct - { - uint16_t server; - uint16_t discovery; - uint16_t mqtt; - } ports; - + char *file; + char database[256]; + char user[256]; + char group[256]; + int log_level; + FILE *log_file; + run_type_t run_type; + uint16_t server_port; + uint16_t discovery_port; + uint16_t mqtt_port; + char content_dir[1024]; + char not_found_file[256]; + char not_found_file_type[256]; + char not_found_content[256]; + char not_found_content_type[256]; struct mg_serve_http_opts http_server_opts; } config_t; -extern config_t *global_config; +extern config_t global_config; -void -config_init(); - -void -config_free(); - -void -config_load_string(char **holder, const char *value); - -void -config_load(config_t *config, const char *cli_config_file); - -void -config_load_file(config_t *config, const char *file_name); - -void -config_load_directory(config_t *config, const char *directory_name); +int +config_load(IniDispatch *disp, void *config_void); #endif /* CORE_CONFIG_H */ diff --git a/include/constants.h b/include/constants.h index d92ad15..329868c 100644 --- a/include/constants.h +++ b/include/constants.h @@ -2,7 +2,6 @@ #define CORE_CONTANTS_H #define SECONDS_PER_DAY 86400 // 60 * 60 * 24 -#define DAYS_PER_WEEK 7 #define SECONDS_PER_MINUTE 60 @@ -15,6 +14,13 @@ */ #define MAX_NAME_LENGTH 128 +/** + * @brief Maximum number of dbs for the databases for the MDB_env + * + * Used when calling mdb_env_set_maxdbs() in database_setup() + */ +#define MDB_MAXDBS 8 + /** * @brief How many milli seconds to wait until poll timeout in main loop */ @@ -22,11 +28,4 @@ #define PIFACE_GPIO_BASE 200 -#define DEFAULT_CONFIG_PATH "emgauwa-core.conf" -#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/core.conf" -#define DEFAULT_DATABASE_PATH "emgauwa-core.sqlite" -#define DEFAULT_DISCOVERY_PORT 4421 -#define DEFAULT_MQTT_PORT 1885 -#define DEFAULT_SERVER_PORT 5000 - #endif /* CORE_CONTANTS_H */ diff --git a/include/database.h b/include/database.h index a97879c..dde8177 100644 --- a/include/database.h +++ b/include/database.h @@ -3,8 +3,6 @@ #include <sqlite3.h> -typedef int database_transaction_lock; - extern sqlite3 *global_database; void @@ -17,14 +15,14 @@ void database_migrate(); -void -database_transaction_begin(database_transaction_lock *lock); +int +database_transaction_begin(); void -database_transaction_commit(const database_transaction_lock *lock); +database_transaction_commit(); void -database_transaction_rollback(const database_transaction_lock *lock); +database_transaction_rollback(); int diff --git a/include/endpoint.h b/include/endpoint.h index c259938..6447487 100644 --- a/include/endpoint.h +++ b/include/endpoint.h @@ -24,7 +24,7 @@ typedef struct int status_code; const char *content_type; size_t content_length; - char *content; + const char *content; int alloced_content; } endpoint_response_t; @@ -53,16 +53,10 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_ void endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); -void -endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length); - void endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length); void endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root); -void -endpoint_response_free_content(endpoint_response_t *response); - #endif /* CORE_ENDPOINT_H */ diff --git a/include/endpoints/api_v1_controllers.h b/include/endpoints/api_v1_controllers.h index 5d96497..6430079 100644 --- a/include/endpoints/api_v1_controllers.h +++ b/include/endpoints/api_v1_controllers.h @@ -4,7 +4,7 @@ #include <router.h> void -api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); +api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); void api_v1_controllers_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); diff --git a/include/endpoints/api_v1_macros.h b/include/endpoints/api_v1_macros.h deleted file mode 100644 index 9a79271..0000000 --- a/include/endpoints/api_v1_macros.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef CORE_ENDPOINTS_API_V1_MACROS_H -#define CORE_ENDPOINTS_API_V1_MACROS_H - -#include <router.h> - -void -api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -void -api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -void -api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -void -api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -void -api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -void -api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response); - -#endif /* CORE_ENDPOINTS_API_V1_MACROS_H */ diff --git a/include/helpers.h b/include/helpers.h index 8b322a7..e2ead83 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -2,19 +2,19 @@ #define CORE_HELPERS_H #include <time.h> -#include <mongoose.h> #include <config.h> +#include <confini.h> int helper_connect_tcp_server(char* host, uint16_t port); +void +helper_parse_cli(int argc, const char **argv, config_t *config); + int helper_get_weekday(const struct tm *time_struct); int helper_drop_privileges(); -char* -find_query_param(struct mg_str query_mg_str, char* search_key); - #endif /* CORE_HELPERS_H */ diff --git a/include/logger.h b/include/logger.h index ba95065..fc207f5 100644 --- a/include/logger.h +++ b/include/logger.h @@ -8,22 +8,15 @@ #include <colors.h> #include <config.h> -#define LOG_NONE INT_MAX - -#ifndef __FILENAME__ - #define __FILENAME__ __FILE__ -#endif - void logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...); -#define LOGGER_NONE(...) logger_log(LOG_NONE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) -#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILE__, __LINE__, __func__, ##__VA_ARGS__) #endif //EMGAUWA_LOGGER_H diff --git a/include/macros.h b/include/macros.h index 1781aad..4242abe 100644 --- a/include/macros.h +++ b/include/macros.h @@ -1,30 +1,6 @@ #ifndef CORE_MACROS_H #define CORE_MACROS_H -#include <logger.h> - -#define M_STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0])) - -#define M_RESPONSE_MSG(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_msg(response, code, content, M_STRLEN(content)); } while(0) -#define M_RESPONSE_TEXT(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_text(response, code, content, M_STRLEN(content)); } while(0) - -#define M_RESPONSE_400_NO_VALID_JSON(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request is not valid json") -#define M_RESPONSE_400_NO_VALID_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid id (uuid)") -#define M_RESPONSE_400_NO_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name") -#define M_RESPONSE_400_NO_VALID_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid name") -#define M_RESPONSE_400_NO_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain an ip address") -#define M_RESPONSE_400_NO_VALID_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid ip address") - -#define M_RESPONSE_403_PROTECTED_SCHEDULE(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 403, "the target schedule is protected") - -#define M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no controller was found for the requested id") -#define M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no relay was found for the requested controller id and relay number") -#define M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id") -#define M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id") -#define M_RESPONSE_404_NO_TAG_FOUND(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the requested tag was not found") - -#define M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to save to the database") -#define M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to read from the database") -#define M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to delete from the database") +#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0])) #endif /* CORE_MACROS_H */ diff --git a/include/models/controller.h b/include/models/controller.h index f12f96a..e144a0d 100644 --- a/include/models/controller.h +++ b/include/models/controller.h @@ -25,16 +25,16 @@ typedef struct } controller_t; void -controller_free(controller_t *controller); +controller_free(controller_t* contoller); int -controller_save(controller_t *controller); +controller_save(controller_t* contoller); int -controller_remove(controller_t *controller); +controller_remove(controller_t* contoller); cJSON* -controller_to_json(controller_t *controller); +controller_to_json(controller_t* contoller); controller_t* controller_get_by_id(int id); diff --git a/include/models/junction_relay_schedule.h b/include/models/junction_relay_schedule.h index e3c81ac..d8ef168 100644 --- a/include/models/junction_relay_schedule.h +++ b/include/models/junction_relay_schedule.h @@ -10,7 +10,4 @@ junction_relay_schedule_remove_for_relay(int relay_id); int junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids); -int* -junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id); - #endif /* CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H */ diff --git a/include/models/macro.h b/include/models/macro.h deleted file mode 100644 index 29b3e70..0000000 --- a/include/models/macro.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CORE_MACRO_H -#define CORE_MACRO_H - -#include <uuid/uuid.h> - -#include <cJSON.h> - -#include <constants.h> -#include <models/macro_action.h> - -typedef struct -{ - int id; - uuid_t uid; - char name[MAX_NAME_LENGTH + 1]; -} macro_t; - -int -macro_save(macro_t *macro); - -int -macro_remove(macro_t *macro); - -void -macro_free(macro_t *macro); - -void -macro_free_list(macro_t **macro); - -cJSON* -macro_to_json(macro_t *macro); - -macro_t* -macro_get_by_id(int id); - -macro_t* -macro_get_by_uid(uuid_t uid); - -macro_t** -macro_get_all(); - -int* -macro_get_target_relay_ids(int macro_id); - -#endif /* CORE_MACRO_H */ diff --git a/include/models/macro_action.h b/include/models/macro_action.h deleted file mode 100644 index a46ed19..0000000 --- a/include/models/macro_action.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef CORE_MODELS_MACRO_ACTION_H -#define CORE_MODELS_MACRO_ACTION_H - -typedef struct -{ - int macro_id; - int relay_id; - int schedule_id; - uint8_t weekday; -} macro_action_t; - -int -macro_action_insert(macro_action_t *macro_action); - -int -macro_action_delete_for_macro(int macro_id); - -macro_action_t** -macro_action_get_for_macro(int macro_id); - -macro_action_t** -macro_action_get_for_macro_and_weekday(int macro_id, int weekday); - -int -macro_action_execute(macro_action_t *macro_action); - -void -macro_action_free_list(macro_action_t **macro_actions); - -int* -macro_action_get_macro_ids_with_schedule(int schedule_id); - -int* -macro_action_get_macro_ids_with_relay(int relay_id); - -#endif /* CORE_MODELS_MACRO_ACTION_H */ diff --git a/include/models/schedule.h b/include/models/schedule.h index c879eec..fcec754 100644 --- a/include/models/schedule.h +++ b/include/models/schedule.h @@ -35,6 +35,9 @@ schedule_free_list(schedule_t **schedule); cJSON* schedule_to_json(schedule_t *schedule); +void +schedule_free_list(schedule_t **schedules_list); + uint16_t* schedule_periods_to_blob(schedule_t *schedule); @@ -62,10 +65,4 @@ schedule_uid_parse(const char *uid_str, uuid_t result); void schedule_uid_unparse(const uuid_t uid, char *result); -void -schedule_get_uid_off(uuid_t target); - -void -schedule_get_uid_on(uuid_t target); - #endif /* CORE_SCHEDULE_H */ diff --git a/shell.nix b/shell.nix deleted file mode 100644 index dac87db..0000000 --- a/shell.nix +++ /dev/null @@ -1,8 +0,0 @@ -with import <nixpkgs> {}; -mkShell { - nativeBuildInputs = [ - cmake - sqlite - util-linux - ]; -} diff --git a/sql/cache.sql b/sql/cache.sql index 3e4ce21..5ca7471 100644 --- a/sql/cache.sql +++ b/sql/cache.sql @@ -1,5 +1,3 @@ --- a key-value table used for the json-cache - CREATE TABLE cache ( key STRING PRIMARY KEY, diff --git a/sql/migration_0.sql b/sql/migration_0.sql index ec39fba..12dda72 100644 --- a/sql/migration_0.sql +++ b/sql/migration_0.sql @@ -1,6 +1,4 @@ --- base migration - -CREATE TABLE controllers +create table controllers ( id INTEGER PRIMARY KEY @@ -16,7 +14,7 @@ CREATE TABLE controllers NOT NULL ); -CREATE TABLE relays +create table relays ( id INTEGER PRIMARY KEY @@ -30,7 +28,7 @@ CREATE TABLE relays ON DELETE CASCADE ); -CREATE TABLE schedules +create table schedules ( id INTEGER PRIMARY KEY @@ -42,7 +40,7 @@ CREATE TABLE schedules periods BLOB ); -CREATE TABLE tags +create table tags ( id INTEGER PRIMARY KEY @@ -52,7 +50,7 @@ CREATE TABLE tags UNIQUE ); -CREATE TABLE junction_tag +create table junction_tag ( tag_id INTEGER NOT NULL @@ -66,7 +64,7 @@ CREATE TABLE junction_tag ON DELETE CASCADE ); -CREATE TABLE junction_relay_schedule +create table junction_relay_schedule ( weekday SMALLINT NOT NULL, @@ -80,4 +78,4 @@ CREATE TABLE junction_relay_schedule ); INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00'); -INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000000000'); +INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05'); diff --git a/sql/migration_1.sql b/sql/migration_1.sql deleted file mode 100644 index 5078763..0000000 --- a/sql/migration_1.sql +++ /dev/null @@ -1,28 +0,0 @@ --- migration to add macros - -CREATE TABLE macros -( - id INTEGER - PRIMARY KEY - AUTOINCREMENT, - uid BLOB - NOT NULL - UNIQUE, - name VARCHAR(128) -); - -CREATE TABLE macro_actions -( - macro_id INTEGER - NOT NULL - REFERENCES macros (id) - ON DELETE CASCADE, - relay_id INTEGER - REFERENCES relays (id) - ON DELETE CASCADE, - schedule_id INTEGER - REFERENCES schedules (id) - ON DELETE CASCADE, - weekday SMALLINT - NOT NULL -); diff --git a/src/cache.c b/src/cache.c index ce70088..2636ead 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,9 +1,7 @@ #include <cache.h> #include <logger.h> -#include <models/junction_relay_schedule.h> -#include <models/junction_tag.h> -#include <models/macro_action.h> #include <sql/cache.h> +#include <models/junction_tag.h> sqlite3 *cache_database; @@ -25,15 +23,8 @@ cache_get_value(char *key) if (s == SQLITE_ROW) { const char *found_value = (const char *)sqlite3_column_text(stmt, 0); - size_t found_value_len = sqlite3_column_bytes(stmt, 0); - - if(result) - { - free(result); - } - result = (char*)malloc(sizeof(char) * (found_value_len + 1)); - strncpy(result, found_value, found_value_len); - result[found_value_len] = '\0'; + result = (char*)malloc(sizeof(char) * (strlen(found_value) + 1)); + strcpy(result, found_value); } else { @@ -41,9 +32,11 @@ cache_get_value(char *key) { break; } - - LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s)); - break; + else + { + LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s)); + break; + } } } @@ -141,19 +134,14 @@ cache_invalidate_schedule(int schedule_id) sprintf(key, "schedule_json:%d", schedule_id); cache_invalidate(key); - int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(schedule_id); - for(int i = 0; relay_ids[i] != 0; ++i) - { - cache_invalidate_relay(relay_ids[i], -1); - } - free(relay_ids); + relay_t **relays = relay_get_with_schedule(schedule_id); - int *macro_ids = macro_action_get_macro_ids_with_schedule(schedule_id); - for(int i = 0; macro_ids[i] != 0; ++i) + for(int i = 0; relays[i] != NULL; ++i) { - cache_invalidate_macro(macro_ids[i]); + cache_invalidate_relay(relays[i]->id, -1); } - free(macro_ids); + + relay_free_list(relays); } @@ -192,13 +180,6 @@ cache_invalidate_relay(int relay_id, int status_relay) { cache_invalidate_controller(controller_id); } - - int *macro_ids = macro_action_get_macro_ids_with_relay(relay_id); - for(int i = 0; macro_ids[i] != 0; ++i) - { - cache_invalidate_macro(macro_ids[i]); - } - free(macro_ids); } @@ -227,34 +208,6 @@ cache_invalidate_controller(int controller_id) cache_invalidate(key); } - - -void -cache_put_json_macro(int macro_id, char *macro_json) -{ - char key[32]; - sprintf(key, "macro_json:%d", macro_id); - cache_insert_value(key, macro_json); -} - -char* -cache_get_json_macro(int macro_id) -{ - char key[32]; - sprintf(key, "macro_json:%d", macro_id); - return cache_get_value(key); -} - -void -cache_invalidate_macro(int macro_id) -{ - char key[32]; - sprintf(key, "macro_json:%d", macro_id); - cache_invalidate(key); -} - - - void cache_invalidate_tagged(int tag_id) { diff --git a/src/command.c b/src/command.c index 8fb0ac4..ec299ad 100644 --- a/src/command.c +++ b/src/command.c @@ -90,17 +90,13 @@ command_schedule_update(schedule_t *schedule) LOGGER_ERR("couldn't find controller for relay %d\n", relays[i]->id); continue; } + controller_free(controller); LOGGER_DEBUG("sending command to controller %s\n", controller->name); - result |= command_send(controller, payload, payload_size); - - controller_free(controller); } relay_free_list(relays); - free(payload); - return result; } @@ -211,6 +207,8 @@ command_controller_name_set(controller_t *controller) int command_send(controller_t *controller, char *payload, uint32_t payload_size) { + int bytes_transferred; + int fd_controller = helper_connect_tcp_server(controller->ip, controller->port); if(fd_controller == -1) @@ -219,12 +217,12 @@ command_send(controller_t *controller, char *payload, uint32_t payload_size) return 1; } - if(send(fd_controller, &payload_size, sizeof(payload_size), 0) <= 0) + if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0) { LOGGER_ERR("error during sending size\n"); return 1; } - if(send(fd_controller, payload, payload_size, 0) <= 0) + if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0) { LOGGER_ERR("error during sending\n"); return 1; diff --git a/src/config.c b/src/config.c index 8eaebdf..a674c86 100644 --- a/src/config.c +++ b/src/config.c @@ -1,380 +1,151 @@ -#include <dirent.h> -#include <errno.h> #include <stdlib.h> #include <string.h> -#include <config.h> -#include <constants.h> #include <logger.h> +#include <config.h> -config_t *global_config; +config_t global_config; + +#define CONFINI_IS_KEY(SECTION, KEY) \ + (ini_array_match(SECTION, disp->append_to, '.', disp->format) && \ + ini_string_match_ii(KEY, disp->data, disp->format)) static int -config_load_log_level(config_t *config, char *value) +config_load_log_level(IniDispatch *disp, config_t *config) { - if(strcmp(value, "debug") == 0) + if(strcasecmp(disp->value, "debug") == 0) { setlogmask(LOG_UPTO(LOG_DEBUG)); - config->logging.level = LOG_DEBUG; + config->log_level = LOG_DEBUG; return 0; } - if(strcmp(value, "info") == 0) + if(strcasecmp(disp->value, "info") == 0) { setlogmask(LOG_UPTO(LOG_INFO)); - config->logging.level = LOG_INFO; + config->log_level = LOG_INFO; return 0; } - if(strcmp(value, "notice") == 0) + if(strcasecmp(disp->value, "notice") == 0) { setlogmask(LOG_UPTO(LOG_NOTICE)); - config->logging.level = LOG_NOTICE; + config->log_level = LOG_NOTICE; return 0; } - if(strcmp(value, "warning") == 0) + if(strcasecmp(disp->value, "warning") == 0) { setlogmask(LOG_UPTO(LOG_WARNING)); - config->logging.level = LOG_WARNING; + config->log_level = LOG_WARNING; return 0; } - if(strcmp(value, "err") == 0) + if(strcasecmp(disp->value, "err") == 0) { setlogmask(LOG_UPTO(LOG_ERR)); - config->logging.level = LOG_ERR; + config->log_level = LOG_ERR; return 0; } - if(strcmp(value, "crit") == 0) + if(strcasecmp(disp->value, "crit") == 0) { setlogmask(LOG_UPTO(LOG_CRIT)); - config->logging.level = LOG_CRIT; + config->log_level = LOG_CRIT; return 0; } - if(strcmp(value, "emerg") == 0) + if(strcasecmp(disp->value, "emerg") == 0) { setlogmask(LOG_UPTO(LOG_EMERG)); - config->logging.level = LOG_EMERG; + config->log_level = LOG_EMERG; return 0; } - LOGGER_WARNING("invalid log-level '%s'\n", value); - + LOGGER_WARNING("invalid log-level '%s'\n", disp->value); return 0; } static int -config_load_log_file(config_t *config, char *value) +config_load_log_file(IniDispatch *disp, config_t *config) { - if(strcmp(value, "stdout") == 0) + if(strcasecmp(disp->value, "stdout") == 0) { - config->logging.file = stdout; + config->log_file = stdout; return 0; } - if(strcmp(value, "stderr") == 0) + if(strcasecmp(disp->value, "stderr") == 0) { - config->logging.file = stderr; + config->log_file = stderr; return 0; } - config->logging.file = fopen(value, "a+"); + config->log_file = fopen(disp->value, "a+"); return 0; } -static void -config_load_section_core(config_t *config, toml_table_t* core) +int +config_load(IniDispatch *disp, void *config_void) { - toml_datum_t config_entry; + config_t *config = (config_t*)config_void; - config_entry = toml_string_in(core, "database"); - if(config_entry.ok) + if(disp->type == INI_KEY) { - config_load_string(&config->database, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "user"); - if(config_entry.ok) - { - config_load_string(&config->user, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "group"); - if(config_entry.ok) - { - config_load_string(&config->group, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "content-dir"); - if(config_entry.ok) - { - config_load_string(&config->content_dir, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "not-found-file"); - if(config_entry.ok) - { - config_load_string(&config->not_found_file, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "not-found-file-type"); - if(config_entry.ok) - { - config_load_string(&config->not_found_file_type, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "not-found-content"); - if(config_entry.ok) - { - config_load_string(&config->not_found_content, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "not-found-content-type"); - if(config_entry.ok) - { - config_load_string(&config->not_found_content_type, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(core, "include"); - if(config_entry.ok) - { - config_load_string(&config->include, config_entry.u.s); - free(config_entry.u.s); - } -} - -static void -config_load_section_logging(config_t *config, toml_table_t* logging) -{ - toml_datum_t config_entry; - - config_entry = toml_string_in(logging, "level"); - if(config_entry.ok) - { - config_load_log_level(config, config_entry.u.s); - free(config_entry.u.s); - } - - config_entry = toml_string_in(logging, "file"); - if(config_entry.ok) - { - config_load_log_file(config, config_entry.u.s); - free(config_entry.u.s); - } -} - -static void -config_load_section_ports(config_t *config, toml_table_t* ports) -{ - toml_datum_t config_entry; - - config_entry = toml_int_in(ports, "server"); - if(config_entry.ok) - { - config->ports.server = config_entry.u.i; - } - - config_entry = toml_int_in(ports, "discovery"); - if(config_entry.ok) - { - config->ports.discovery = config_entry.u.i; - } - - config_entry = toml_int_in(ports, "mqtt"); - if(config_entry.ok) - { - config->ports.mqtt = config_entry.u.i; - } -} - -void -config_init() -{ - global_config = calloc(1, sizeof(config_t)); - - config_load_string(&global_config->database, DEFAULT_DATABASE_PATH); - - global_config->ports.discovery = DEFAULT_DISCOVERY_PORT; - global_config->ports.mqtt = DEFAULT_MQTT_PORT; - global_config->ports.server = DEFAULT_SERVER_PORT; - - global_config->logging.level = LOG_DEBUG; - global_config->logging.file = stdout; - - global_config->user = NULL; - global_config->group = NULL; - - global_config->include = NULL; - - config_load_string(&global_config->content_dir, "."); - config_load_string(&global_config->not_found_file, "404.html"); - config_load_string(&global_config->not_found_file_type, "text/html"); - config_load_string(&global_config->not_found_content, "404 - NOT FOUND"); - config_load_string(&global_config->not_found_content_type, "text/plain"); -} - -void -config_free() -{ - free(global_config->include); - free(global_config->database); - free(global_config->user); - free(global_config->group); - free(global_config->content_dir); - free(global_config->not_found_file); - free(global_config->not_found_file_type); - free(global_config->not_found_content); - free(global_config->not_found_content_type); - - free(global_config); -} - -void -config_load_string(char **holder, const char *value) -{ - if(*holder) - { - free(*holder); - } - size_t value_len = strlen(value); - - char *new_holder = malloc(sizeof(char) * (value_len + 1)); - strcpy(new_holder, value); - new_holder[value_len] = '\0'; - - *holder = new_holder; -} - -static int -config_try_file(const char *path) -{ - if(access(path, F_OK) != 0) - { - return 1; - } - if(access(path, R_OK) != 0) - { - return 1; + if(CONFINI_IS_KEY("core", "database")) + { + strcpy(config->database, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "user")) + { + strcpy(config->user, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "group")) + { + strcpy(config->group, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "content-dir")) + { + strcpy(config->content_dir, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "not-found-file")) + { + strcpy(config->not_found_file, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "not-found-file-type")) + { + strcpy(config->not_found_file_type, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "not-found-content")) + { + strcpy(config->not_found_content, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "not-found-content-type")) + { + strcpy(config->not_found_content_type, disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "log-level")) + { + return config_load_log_level(disp, config); + } + if(CONFINI_IS_KEY("core", "log-file")) + { + return config_load_log_file(disp, config); + } + if(CONFINI_IS_KEY("core", "server-port")) + { + config->server_port = atoi(disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "discovery-port")) + { + config->discovery_port = atoi(disp->value); + return 0; + } + if(CONFINI_IS_KEY("core", "mqtt-port")) + { + config->mqtt_port = atoi(disp->value); + return 0; + } } return 0; } - -void -config_load(config_t *config, const char *cli_config_file) -{ - if(cli_config_file) - { - if(config_try_file(cli_config_file) == 0) - { - config_load_file(config, cli_config_file); - return; - } - LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file); - exit(1); - } - if(config_try_file(DEFAULT_CONFIG_PATH) == 0) - { - config_load_file(config, DEFAULT_CONFIG_PATH); - return; - } - if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0) - { - config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH); - return; - } -} - -void -config_load_file(config_t *config, const char *file_name) -{ - FILE *fp; - toml_table_t* config_toml; - char errbuf[256]; - - /* Open the file and parse content */ - fp = fopen(file_name, "r"); - if(fp == NULL) { - LOGGER_CRIT("unable to open config file '%s'\n", file_name); - exit(1); - } - config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf)); - fclose(fp); - if(config_toml == NULL) { - LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf); - exit(1); - } - - toml_table_t* core = toml_table_in(config_toml, "core"); - if(core) - { - config_load_section_core(config, core); - } - - toml_table_t* logging = toml_table_in(config_toml, "logging"); - if(logging) - { - config_load_section_logging(config, logging); - } - - toml_table_t* ports = toml_table_in(config_toml, "ports"); - if(ports) - { - config_load_section_ports(config, ports); - } - - toml_free(config_toml); - - LOGGER_DEBUG("Loaded config from %s\n", file_name); -} - -void -config_load_directory(config_t *config, const char *directory_name) -{ - struct dirent *directory_entry; - DIR *directory; - - (void)config; - - directory = opendir(directory_name); - if(directory == NULL) - { - LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno)); - exit(1); - } - - while((directory_entry = readdir(directory)) != NULL) - { - struct stat sb; - const char *entry_name = directory_entry->d_name; - - size_t copied = 0; - - // Add 2 for '/' and '\0'. - size_t path_len = strlen(directory_name) + strlen(entry_name) + 1; - char *path = malloc(sizeof(char) * (path_len + 1)); - path[0] = '\0'; - - strncat(path + copied, directory_name, path_len - copied); - copied = strlen(path); - if(path[copied - 1] != '/') - { - strncat(path + copied, "/", path_len - copied); - copied = strlen(path); - } - strncat(path + copied, entry_name, path_len - copied); - - if(stat(path, &sb)) - { - LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno)); - } - if(S_ISREG(sb.st_mode)) - { - config_load_file(config, path); - } - free(path); - } - - closedir(directory); -} diff --git a/src/database.c b/src/database.c index 05474f8..02fd75a 100644 --- a/src/database.c +++ b/src/database.c @@ -5,15 +5,14 @@ #include <database.h> #include <sql/migration_0.h> -#include <sql/migration_1.h> sqlite3 *global_database; -static database_transaction_lock *transaction_lock; +static int in_transaction; void database_init() { - int rc = sqlite3_open(global_config->database, &global_database); + int rc = sqlite3_open(global_config.database, &global_database); if(rc) { @@ -21,12 +20,10 @@ database_init() exit(1); } - LOGGER_DEBUG("Opened database %s\n", global_config->database); - database_migrate(); sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0); - transaction_lock = NULL; + in_transaction = 0; } void @@ -35,35 +32,11 @@ database_free() sqlite3_close(global_database); } -static void -database_migrate_step_simple(int level, const char* sql_migration) -{ - LOGGER_INFO("migrating LEVEL %d\n", level); - char* err_msg; - - sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL); - - int rc = sqlite3_exec(global_database, sql_migration, NULL, NULL, &err_msg); - if(rc) - { - LOGGER_CRIT("couldn't migrate LEVEL %d (%s)\n", level, err_msg); - sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - exit(1); - } - - LOGGER_DEBUG("storing new user_version %d\n", level + 1); - char pragma_query[32]; - sprintf(pragma_query, "PRAGMA user_version=%d;", level + 1); - sqlite3_exec(global_database, pragma_query, 0, 0, 0); - - sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL); -} - void database_migrate() { uint16_t version_num = 0; - int s; + int s, rc; sqlite3_stmt *stmt; sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL); s = sqlite3_step(stmt); @@ -76,52 +49,61 @@ database_migrate() version_num = 0; } + uint16_t new_version_num = version_num; + char* err_msg; + sqlite3_finalize(stmt); switch(version_num) { case 0: - database_migrate_step_simple(0, (const char*)sql_migration_0_sql); - __attribute__ ((fallthrough)); - case 1: - database_migrate_step_simple(1, (const char*)sql_migration_1_sql); - __attribute__ ((fallthrough)); + LOGGER_INFO("migrating LEVEL 0\n"); + rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg); + if(rc) + { + LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg); + exit(1); + } + new_version_num = 1; default: break; } + + char pragma_query[32]; + sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num); + sqlite3_exec(global_database, pragma_query, 0, 0, 0); + LOGGER_DEBUG("storing new user_version %d\n", new_version_num); + + return; } -void -database_transaction_begin(database_transaction_lock *lock) +int +database_transaction_begin() { - if(transaction_lock == NULL) + if(!in_transaction) { LOGGER_DEBUG("beginning transaction\n"); sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL); - transaction_lock = lock; + in_transaction = 1; + return 1; } + return 0; } void -database_transaction_commit(const database_transaction_lock *lock) +database_transaction_commit() { - if(transaction_lock == lock) - { - LOGGER_DEBUG("commiting transaction\n"); - sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL); - transaction_lock = NULL; - } + LOGGER_DEBUG("commiting transaction\n"); + sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL); + in_transaction = 0; } void -database_transaction_rollback(const database_transaction_lock *lock) +database_transaction_rollback() { - if(transaction_lock == lock) - { - LOGGER_DEBUG("rolling back transaction\n"); - sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); - transaction_lock = NULL; - } + LOGGER_DEBUG("rolling back transaction\n"); + sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); + in_transaction = 0; } int @@ -144,10 +126,12 @@ database_helper_get_id(sqlite3_stmt *stmt) { break; } - - LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s)); - sqlite3_finalize(stmt); - return 0; + else + { + LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s)); + sqlite3_finalize(stmt); + return 0; + } } } @@ -186,14 +170,12 @@ database_helper_get_ids(sqlite3_stmt *stmt) { break; } - - if(result) + else { - free(result); + LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s)); + sqlite3_finalize(stmt); + return NULL; } - LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s)); - sqlite3_finalize(stmt); - return NULL; } } @@ -216,15 +198,8 @@ database_helper_get_string(sqlite3_stmt *stmt) if (s == SQLITE_ROW) { const char *found_string = (const char *)sqlite3_column_text(stmt, 0); - size_t found_string_len = sqlite3_column_bytes(stmt, 0); - - if(result) - { - free(result); - } - result = (char*)malloc(sizeof(char) * (found_string_len + 1)); - strncpy(result, found_string, found_string_len); - result[found_string_len] = '\0'; + result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1)); + strcpy(result, found_string); } else { @@ -232,14 +207,12 @@ database_helper_get_string(sqlite3_stmt *stmt) { break; } - - if(result) + else { - free(result); + LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s)); + sqlite3_finalize(stmt); + return NULL; } - LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s)); - sqlite3_finalize(stmt); - return NULL; } } @@ -263,13 +236,12 @@ database_helper_get_strings(sqlite3_stmt *stmt) if (s == SQLITE_ROW) { const char *new_string = (const char *)sqlite3_column_text(stmt, 0); - size_t new_string_len = sqlite3_column_bytes(stmt, 0); + int new_string_len = strlen(new_string); row++; result = (char**)realloc(result, sizeof(char*) * (row + 1)); result[row - 1] = malloc(sizeof(char) * (new_string_len + 1)); - strncpy(result[row - 1], new_string, new_string_len); - result[row - 1][new_string_len] = '\0'; + strcpy(result[row - 1], new_string); } else { @@ -277,9 +249,11 @@ database_helper_get_strings(sqlite3_stmt *stmt) { break; } - - LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s)); - break; + else + { + LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s)); + break; + } } } sqlite3_finalize(stmt); diff --git a/src/endpoint.c b/src/endpoint.c index d56838f..a56f277 100644 --- a/src/endpoint.c +++ b/src/endpoint.c @@ -14,7 +14,7 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_ response->status_code = 0; - mg_serve_http(nc, hm, global_config->http_server_opts); + mg_serve_http(nc, hm, global_config.http_server_opts); } void @@ -24,61 +24,27 @@ endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpo (void)hm; (void)nc; - if(access(global_config->not_found_file, R_OK) != -1) + if(access(global_config.not_found_file, R_OK) != -1) { - struct mg_str mime_type = mg_mk_str(global_config->not_found_file_type); + struct mg_str mime_type = mg_mk_str(global_config.not_found_file_type); response->status_code = 0; - mg_http_serve_file(nc, hm, global_config->not_found_file, mime_type, mg_mk_str("")); + mg_http_serve_file(nc, hm, global_config.not_found_file, mime_type, mg_mk_str("")); } else { LOGGER_DEBUG("404 file not found\n"); - - endpoint_response_free_content(response); - response->status_code = 404; - response->content_type = global_config->not_found_content_type; - response->content_length = strlen(global_config->not_found_content); - response->content = global_config->not_found_content; + response->content_type = global_config.not_found_content_type; + response->content_length = strlen(global_config.not_found_content); + response->content = global_config.not_found_content; response->alloced_content = false; } } -void -endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length) -{ - endpoint_response_free_content(response); - - cJSON *json; - - json = cJSON_CreateObject(); - cJSON *json_msg; - - if(content_length) - { - json_msg = cJSON_CreateStringReference(content); - } - else - { - json_msg = cJSON_CreateString(content); - } - if(json_msg == NULL) - { - endpoint_response_text(response, status_code, content, content_length); - return; - } - cJSON_AddItemToObject(json, "msg", json_msg); - - endpoint_response_json(response, status_code, json); - - cJSON_Delete(json); -} - void endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length) { - endpoint_response_free_content(response); if(content == NULL) { content = ""; @@ -87,27 +53,22 @@ endpoint_response_text(endpoint_response_t *response, int status_code, const cha response->status_code = status_code; response->content_type = "text/plain"; - if(content_length) + if(content_length >= 0) { response->content_length = content_length; response->alloced_content = false; - response->content = (char*)content; } else { response->content_length = strlen(content); response->alloced_content = true; - - response->content = malloc(sizeof(char) * (response->content_length + 1)); - strcpy(response->content, content); - response->content[response->content_length] = '\0'; } + response->content = content; } void endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root) { - endpoint_response_free_content(response); if(json_root != NULL) { char *json_str = cJSON_Print(json_root); @@ -123,16 +84,8 @@ endpoint_response_json(endpoint_response_t *response, int status_code, const cJS } } - M_RESPONSE_MSG(LOGGER_ERR, response, 500, "failed to print json"); -} + LOGGER_ERR("failed to print schedule json\n"); -void -endpoint_response_free_content(endpoint_response_t *response) -{ - if(response->alloced_content) - { - free(response->content); - response->content = NULL; - response->alloced_content = false; - } + static const char content[] = "failed to print json"; + endpoint_response_text(response, status_code, content, STRLEN(content)); } diff --git a/src/endpoints/api_v1_controllers_STR.c b/src/endpoints/api_v1_controllers_STR.c index 5d5c370..eb4c24d 100644 --- a/src/endpoints/api_v1_controllers_STR.c +++ b/src/endpoints/api_v1_controllers_STR.c @@ -18,7 +18,10 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -26,7 +29,10 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } LOGGER_DEBUG("returning controller for uid '%s'\n", args[0].value.v_str); @@ -47,7 +53,10 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -55,7 +64,10 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } LOGGER_DEBUG("starting overwrite for controller %s\n", args[0].value.v_str); @@ -64,64 +76,70 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en if(json == NULL) { + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); controller_free(controller); - - M_RESPONSE_400_NO_VALID_JSON(response); return; } cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name"); if(json_name) { - if(!cJSON_IsString(json_name) || json_name->valuestring == NULL) + if(cJSON_IsString(json_name) && json_name->valuestring) { + strncpy(controller->name, json_name->valuestring, MAX_NAME_LENGTH); + controller->name[MAX_NAME_LENGTH] = '\0'; + LOGGER_DEBUG("new controller name: %s\n", controller->name); + } + else + { + static const char content[] = "the given name is no valid string"; + endpoint_response_text(response, 400, content, STRLEN(content)); cJSON_Delete(json); controller_free(controller); - - M_RESPONSE_400_NO_VALID_NAME(response); return; } - - strncpy(controller->name, json_name->valuestring, MAX_NAME_LENGTH); - controller->name[MAX_NAME_LENGTH] = '\0'; - LOGGER_DEBUG("new controller name: %s\n", controller->name); } cJSON *json_ip = cJSON_GetObjectItemCaseSensitive(json, "ip"); if(json_ip) { - if(!cJSON_IsString(json_ip) || json_ip->valuestring == NULL) + if(cJSON_IsString(json_ip) && json_ip->valuestring) { - cJSON_Delete(json); - controller_free(controller); - - M_RESPONSE_400_NO_IP(response); - return; - } - - unsigned char buf[sizeof(struct in_addr)]; - if(inet_pton(AF_INET, json_ip->valuestring, buf)) - { - strncpy(controller->ip, json_ip->valuestring, IP_LENGTH); - controller->ip[IP_LENGTH] = '\0'; - LOGGER_DEBUG("new controller ip: %s\n", controller->ip); + unsigned char buf[sizeof(struct in_addr)]; + if(inet_pton(AF_INET, json_ip->valuestring, buf)) + { + strncpy(controller->ip, json_ip->valuestring, IP_LENGTH); + controller->ip[IP_LENGTH] = '\0'; + LOGGER_DEBUG("new controller ip: %s\n", controller->ip); + } + else + { + static const char content[] = "the given ip address is no valid IPv4 address"; + endpoint_response_text(response, 400, content, STRLEN(content)); + cJSON_Delete(json); + controller_free(controller); + return; + } } else { + static const char content[] = "the given ip address is no valid string"; + endpoint_response_text(response, 400, content, STRLEN(content)); cJSON_Delete(json); controller_free(controller); - - M_RESPONSE_400_NO_VALID_IP(response); return; } } if(controller_save(controller)) { + LOGGER_ERR("failed to save controller\n"); controller_free(controller); cJSON_Delete(json); - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); + static const char content[] = "failed to save controller to database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } LOGGER_DEBUG("saved controller %s\n", args[0].value.v_str); @@ -147,7 +165,10 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm, uuid_t target_uid; if(uuid_parse(target_uid_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -155,17 +176,25 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm, if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } if(controller_remove(controller)) { - M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response); + LOGGER_ERR("failed to remove controller from database\n"); + + static const char content[] = "failed to remove controller from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); } else { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted controller"); + LOGGER_DEBUG("deleted controller %s\n", args[0].value.v_str); + endpoint_response_text(response, 200, "", 0); } controller_free(controller); + return; } diff --git a/src/endpoints/api_v1_controllers_STR_relays.c b/src/endpoints/api_v1_controllers_STR_relays.c index ad8a83e..6accc90 100644 --- a/src/endpoints/api_v1_controllers_STR_relays.c +++ b/src/endpoints/api_v1_controllers_STR_relays.c @@ -15,7 +15,10 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -23,7 +26,10 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } diff --git a/src/endpoints/api_v1_controllers_STR_relays_INT.c b/src/endpoints/api_v1_controllers_STR_relays_INT.c index d470ef2..738ad39 100644 --- a/src/endpoints/api_v1_controllers_STR_relays_INT.c +++ b/src/endpoints/api_v1_controllers_STR_relays_INT.c @@ -18,7 +18,10 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -26,18 +29,21 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } - int controller_id = controller->id; - controller_free(controller); - - relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int); + relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int); if(!relay) { - M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response); + LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str); + + static const char content[] = "no relay for this controller found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } @@ -47,6 +53,7 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess endpoint_response_json(response, 200, json); cJSON_Delete(json); relay_free(relay); + controller_free(controller); } void @@ -58,7 +65,10 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -66,21 +76,23 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } - int controller_id = controller->id; - int controller_relay_count = controller->relay_count; - controller_free(controller); - - if(args[1].value.v_int > controller_relay_count) + if(args[1].value.v_int > controller->relay_count) { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "relay number is too high for this controller"); + LOGGER_DEBUG("relay num %d is too high for %s\n", args[1].value.v_int, args[0].value.v_str); + + static const char content[] = "relay number is too high for this controller"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } - relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int); + relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int); if(!relay) { @@ -90,7 +102,7 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess relay->number = args[1].value.v_int; snprintf(relay->name, MAX_NAME_LENGTH, "Relay %d", relay->number); relay->name[MAX_NAME_LENGTH] = '\0'; - relay->controller_id = controller_id; + relay->controller_id = controller->id; uuid_t tmp_uuid; memset(tmp_uuid, 0, sizeof(uuid_t)); @@ -105,13 +117,15 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess relay->active_schedule = relay->schedules[helper_get_weekday(time_struct)]; } + controller_free(controller); LOGGER_DEBUG("overwriting relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str); cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len); if(json == NULL) { - M_RESPONSE_400_NO_VALID_JSON(response); + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -135,17 +149,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess cJSON *json_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_schedule, "id"); if(!cJSON_IsString(json_schedule_uid) || (json_schedule_uid->valuestring == NULL)) { + LOGGER_DEBUG("schedules[%d] is missing uid\n", schedule_position); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one schedule is missing an id"); + static const char content[] = "at least one schedule is missing an id"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } uuid_t target_uid; if(schedule_uid_parse(json_schedule_uid->valuestring, target_uid)) { + LOGGER_DEBUG("schedules[%d] has bad uid\n", schedule_position); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "at least one schedule has a bad id"); + static const char content[] = "at least one schedule has a bad id"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -172,9 +190,11 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess uuid_t target_uid; if(schedule_uid_parse(json_active_schedule_uid->valuestring, target_uid)) { + LOGGER_DEBUG("active_schedule has bad uid\n"); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "active schedule has a bad uid"); + static const char content[] = "active_schedule has a bad id"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } relay->schedules[day_of_week] = schedule_get_by_uid_or_off(target_uid); @@ -182,15 +202,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess } } - database_transaction_lock lock; - database_transaction_begin(&lock); + int opened_transaction = database_transaction_begin(); if(relay_save(relay)) { - database_transaction_rollback(&lock); + LOGGER_ERR("failed to save relay\n"); + + if(opened_transaction) + { + database_transaction_rollback(); + } + cJSON_Delete(json); - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); + static const char content[] = "failed to save relay to database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } @@ -209,12 +235,19 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess { if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL)) { - database_transaction_rollback(&lock); + LOGGER_DEBUG("invalid tag in tags\n"); + + if(opened_transaction) + { + database_transaction_rollback(); + } + relay_free(relay); cJSON_Delete(json); free(tag_ids); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "invalid tag in tags"); + static const char content[] = "invalid tag in tags"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } const char *tag = json_tag->valuestring; @@ -226,12 +259,13 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess } tag_ids[i++] = tag_id; } - junction_tag_insert_list(tag_ids, relay->id, 0, json_tags_count); - free(tag_ids); } - database_transaction_commit(&lock); + if(opened_transaction) + { + database_transaction_commit(); + } cJSON_Delete(json); json = relay_to_json(relay, 0); diff --git a/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c b/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c index 9398b64..22b158a 100644 --- a/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c +++ b/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c @@ -18,7 +18,10 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht uuid_t target_uid; if(uuid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -26,18 +29,21 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht if(!controller) { - M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no controller for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } - int controller_id = controller->id; - controller_free(controller); - - relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int); + relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int); if(!relay) { - M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response); + LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str); + + static const char content[] = "no relay for this controller found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } @@ -58,6 +64,7 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht LOGGER_DEBUG("commanding pulse to relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str); command_relay_pulse(relay, duration); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "sent pulse"); + endpoint_response_text(response, 200, "", 0); relay_free(relay); + controller_free(controller); } diff --git a/src/endpoints/api_v1_controllers_discover.c b/src/endpoints/api_v1_controllers_discover.c index 00e7042..d2e00d7 100644 --- a/src/endpoints/api_v1_controllers_discover.c +++ b/src/endpoints/api_v1_controllers_discover.c @@ -19,8 +19,7 @@ typedef enum static int bind_tcp_server(const char *addr, const char *port, int max_client_backlog) { - struct addrinfo hints; - struct addrinfo *res; + struct addrinfo hints, *res; int fd; int status; @@ -106,7 +105,7 @@ send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length } void -api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) +api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) { (void)nc; (void)hm; @@ -115,7 +114,13 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h if(discover_server_port == -1) { - M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to prepare discovery"); + LOGGER_ERR("failed to get server port for discovery\n"); + static const char content[] = ""; + response->status_code = 500; + response->content_type = "text/plain"; + response->content_length = STRLEN(content);; + response->content = content; + response->alloced_content = false; return; } @@ -123,16 +128,21 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h payload[0] = discover_server_port; LOGGER_DEBUG("sending udp broadcast\n"); - if(send_udp_broadcast("255.255.255.255", global_config->ports.discovery, payload, sizeof(payload)) < 0) + if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0) { - M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to send discovery broadcast"); + LOGGER_ERR("failed to send UDP broadcast\n"); + static const char content[] = ""; + response->status_code = 500; + response->content_type = "text/plain"; + response->content_length = STRLEN(content);; + response->content = content; + response->alloced_content = false; return; } struct sockaddr_storage their_addr; socklen_t addr_size; - int s_ret; - int client_fd; + int client_fd, s_ret; fd_set accept_fds; struct timeval timeout; @@ -155,147 +165,145 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h { break; } - - if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0) + else { - LOGGER_ERR("error accepting client %s\n", strerror(errno)); - continue; - } - - size_t payload_length; - - if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0) - { - LOGGER_ERR("error receiving header from client\n"); - continue; - } - - char *answer_payload = (char*)malloc((payload_length + 1)); - if(recv(client_fd, answer_payload, payload_length, 0) <= 0) - { - LOGGER_ERR("error receiving payload from client\n"); - continue; - } - - struct sockaddr_in addr; - socklen_t client_addr_size = sizeof(struct sockaddr_in); - if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0) - { - - LOGGER_ERR("error receiving payload from client\n"); - continue; - } - - LOGGER_DEBUG("received info for discovered controller\n"); - - uuid_t discovered_id; - - mpack_tree_t tree; - mpack_tree_init_data(&tree, answer_payload, payload_length); - mpack_tree_parse(&tree); - mpack_node_t root = mpack_tree_root(&tree); - - memcpy(discovered_id, mpack_node_data(mpack_node_map_uint(root, DISCOVERY_MAPPING_ID)), sizeof(uuid_t)); - - uint16_t discovered_command_port = mpack_node_u16(mpack_node_map_uint(root, DISCOVERY_MAPPING_COMMAND_PORT)); - uint8_t discovered_relay_count = mpack_node_u8(mpack_node_map_uint(root, DISCOVERY_MAPPING_RELAY_COUNT)); - const char *discovered_name = mpack_node_str(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME)); - size_t discovered_name_len = mpack_node_strlen(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME)); - - if(discovered_name_len > MAX_NAME_LENGTH) - { - discovered_name_len = MAX_NAME_LENGTH; - } - - bool found_discovered_in_list = 0; - - for(int i = 0; known_controllers[i] != NULL; i++) - { - if(!found_discovered_in_list) + if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0) { - if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0) + LOGGER_ERR("error accepting client %s\n", strerror(errno)); + continue; + } + + size_t payload_length; + + if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0) + { + LOGGER_ERR("error receiving header from client\n"); + continue; + } + + char *answer_payload = (char*)malloc((payload_length)); + ssize_t bytes_transferred; + + if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0) + { + LOGGER_ERR("error receiving payload from client\n"); + continue; + } + + struct sockaddr_in addr; + socklen_t client_addr_size = sizeof(struct sockaddr_in); + if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0) + { + + LOGGER_ERR("error receiving payload from client\n"); + continue; + } + + LOGGER_DEBUG("received info for discovered controller\n"); + + uuid_t discovered_id; + + mpack_tree_t tree; + mpack_tree_init_data(&tree, answer_payload, payload_length); + mpack_tree_parse(&tree); + mpack_node_t root = mpack_tree_root(&tree); + + memcpy(discovered_id, mpack_node_data(mpack_node_map_uint(root, DISCOVERY_MAPPING_ID)), sizeof(uuid_t)); + + uint16_t discovered_command_port = mpack_node_u16(mpack_node_map_uint(root, DISCOVERY_MAPPING_COMMAND_PORT)); + uint8_t discovered_relay_count = mpack_node_u8(mpack_node_map_uint(root, DISCOVERY_MAPPING_RELAY_COUNT)); + const char *discovered_name = mpack_node_str(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME)); + size_t discovered_name_len = mpack_node_strlen(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME)); + + if(discovered_name_len > MAX_NAME_LENGTH) + { + discovered_name_len = MAX_NAME_LENGTH; + } + + bool found_discovered_in_list = 0; + + for(int i = 0; known_controllers[i] != NULL; i++) + { + if(!found_discovered_in_list) { - LOGGER_DEBUG("rediscovered a known controller at %s\n", inet_ntoa(addr.sin_addr)); + if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0) + { + LOGGER_DEBUG("rediscovered a known controller at %s\n", inet_ntoa(addr.sin_addr)); - known_controllers[i]->active = 1; - strncpy(known_controllers[i]->name, discovered_name, discovered_name_len); - known_controllers[i]->name[discovered_name_len] = '\0'; + known_controllers[i]->active = 1; + strncpy(known_controllers[i]->name, discovered_name, discovered_name_len); + strcpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr)); + known_controllers[i]->name[discovered_name_len] = '\0'; + known_controllers[i]->port = discovered_command_port; + known_controllers[i]->relay_count = discovered_relay_count; - strncpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr), IP_LENGTH); - known_controllers[i]->ip[IP_LENGTH] = '\0'; + controller_save(known_controllers[i]); + controller_free(known_controllers[i]); - known_controllers[i]->port = discovered_command_port; - known_controllers[i]->relay_count = discovered_relay_count; - - controller_save(known_controllers[i]); - controller_free(known_controllers[i]); - - found_discovered_in_list = 1; + found_discovered_in_list = 1; + known_controllers[i] = known_controllers[i + 1]; + } + } + else + { known_controllers[i] = known_controllers[i + 1]; } } - else + + if(!found_discovered_in_list) { - known_controllers[i] = known_controllers[i + 1]; - } - } + LOGGER_DEBUG("discovered a new controller at %s\n", inet_ntoa(addr.sin_addr)); - if(!found_discovered_in_list) - { - LOGGER_DEBUG("discovered a new controller at %s\n", inet_ntoa(addr.sin_addr)); + controller_t *discovered_controller = malloc(sizeof(controller_t)); + discovered_controller->id = 0; + strcpy(discovered_controller->ip, inet_ntoa(addr.sin_addr)); + memcpy(discovered_controller->uid, discovered_id, sizeof(uuid_t)); + strncpy(discovered_controller->name, discovered_name, discovered_name_len); + discovered_controller->name[discovered_name_len] = '\0'; + discovered_controller->relay_count = discovered_relay_count; + discovered_controller->port = discovered_command_port; + discovered_controller->active = 1; - controller_t *discovered_controller = malloc(sizeof(controller_t)); - discovered_controller->id = 0; + controller_save(discovered_controller); - strncpy(discovered_controller->ip, inet_ntoa(addr.sin_addr), IP_LENGTH + 1); - discovered_controller->ip[IP_LENGTH] = '\0'; - - uuid_copy(discovered_controller->uid, discovered_id); - strncpy(discovered_controller->name, discovered_name, discovered_name_len); - discovered_controller->name[discovered_name_len] = '\0'; - discovered_controller->relay_count = discovered_relay_count; - discovered_controller->port = discovered_command_port; - discovered_controller->active = 1; - - controller_save(discovered_controller); - - // TODO get relays during discovery and don't create empty ones - relay_t **discovered_relays = malloc(sizeof(relay_t*) * (discovered_controller->relay_count + 1)); - for(int i = 0; i < discovered_controller->relay_count; ++i) - { - relay_t *new_relay = malloc(sizeof(relay_t)); - new_relay->id = 0; - sprintf(new_relay->name, "Relay %d", i + 1); - new_relay->number = i; - new_relay->controller_id = discovered_controller->id; - - uuid_t tmp_uuid; - memset(tmp_uuid, 0, sizeof(uuid_t)); - memcpy(tmp_uuid, "off", 3); - - for(int i = 0; i < 7; ++i) + // TODO get relays during discovery and don't create empty ones + relay_t **discovered_relays = malloc(sizeof(relay_t*) * (discovered_controller->relay_count + 1)); + for(int i = 0; i < discovered_controller->relay_count; ++i) { - new_relay->schedules[i] = schedule_get_by_uid(tmp_uuid); + relay_t *new_relay = malloc(sizeof(relay_t)); + new_relay->id = 0; + sprintf(new_relay->name, "Relay %d", i + 1); + new_relay->number = i; + new_relay->controller_id = discovered_controller->id; + + uuid_t tmp_uuid; + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); + + for(int i = 0; i < 7; ++i) + { + new_relay->schedules[i] = schedule_get_by_uid(tmp_uuid); + } + time_t timestamp = time(NULL); + struct tm *time_struct = localtime(×tamp); + new_relay->active_schedule = new_relay->schedules[helper_get_weekday(time_struct)]; + + relay_save(new_relay); + + discovered_relays[i] = new_relay; } - time_t timestamp = time(NULL); - struct tm *time_struct = localtime(×tamp); - new_relay->active_schedule = new_relay->schedules[helper_get_weekday(time_struct)]; + discovered_relays[discovered_controller->relay_count] = NULL; + discovered_controller->relays = discovered_relays; - relay_save(new_relay); - - discovered_relays[i] = new_relay; + controller_free(discovered_controller); } - discovered_relays[discovered_controller->relay_count] = NULL; - discovered_controller->relays = discovered_relays; + mpack_tree_destroy(&tree); + free(answer_payload); - controller_free(discovered_controller); + discover_answer_buf[0] = 0; // TODO add discovery return codes + send(client_fd, discover_answer_buf, sizeof(uint8_t), 0); + close(client_fd); } - mpack_tree_destroy(&tree); - free(answer_payload); - - discover_answer_buf[0] = 0; // TODO add discovery return codes - send(client_fd, discover_answer_buf, sizeof(uint8_t), 0); - close(client_fd); } for(int i = 0; known_controllers[i] != NULL; i++) { diff --git a/src/endpoints/api_v1_macros.c b/src/endpoints/api_v1_macros.c deleted file mode 100644 index e9cc5ac..0000000 --- a/src/endpoints/api_v1_macros.c +++ /dev/null @@ -1,240 +0,0 @@ -#include <cJSON.h> -#include <macros.h> -#include <constants.h> -#include <database.h> -#include <endpoints/api_v1_macros.h> -#include <logger.h> -#include <models/macro.h> -#include <models/macro_action.h> -#include <models/schedule.h> -#include <models/relay.h> -#include <models/controller.h> - -void -api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)args; - (void)hm; - (void)nc; - - macro_t** all_macros = macro_get_all(); - - cJSON *json = cJSON_CreateArray(); - - for(int i = 0; all_macros[i] != NULL; ++i) - { - cJSON_AddItemToArray(json, macro_to_json(all_macros[i])); - free(all_macros[i]); - } - - endpoint_response_json(response, 200, json); - cJSON_Delete(json); - free(all_macros); -} - -void -api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)args; - (void)nc; - - cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len); - - if(json == NULL) - { - M_RESPONSE_400_NO_VALID_JSON(response); - return; - } - - cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name"); - if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL)) - { - M_RESPONSE_400_NO_NAME(response); - return; - } - - cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions"); - if(!cJSON_IsArray(json_actions)) - { - cJSON_Delete(json); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contains actions for the macro"); - return; - } - - database_transaction_lock lock; - database_transaction_begin(&lock); - - macro_t *new_macro = malloc(sizeof(macro_t)); - - new_macro->id = 0; - uuid_generate(new_macro->uid); - - strncpy(new_macro->name, json_name->valuestring, MAX_NAME_LENGTH); - new_macro->name[MAX_NAME_LENGTH] = '\0'; - - if(macro_save(new_macro)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); - return; - } - - cJSON *json_action; - cJSON_ArrayForEach(json_action, json_actions) - { - cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday"); - if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without weekday"); - return; - } - - cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule"); - if(!cJSON_IsObject(json_action_schedule)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule"); - return; - } - cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id"); - if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule id"); - return; - } - uuid_t action_schedule_uid; - if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid schedule id"); - return; - } - - schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid); - if(action_schedule == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the schedule for at least one action was not found"); - return; - } - - int action_schedule_id = action_schedule->id; - schedule_free(action_schedule); - - - cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay"); - if(!cJSON_IsObject(json_action_relay)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay"); - return; - } - cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number"); - if(!cJSON_IsNumber(json_action_relay_number)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay number"); - return; - } - cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id"); - if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay controller id"); - return; - } - uuid_t action_controller_uid; - if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid relay controller id"); - return; - } - - controller_t *action_controller = controller_get_by_uid(action_controller_uid); - if(action_controller == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the controller for at least one action relay was not found"); - return; - } - - int controller_id = action_controller->id; - int relay_num = json_action_relay_number->valueint; - - controller_free(action_controller); - - relay_t *action_relay = relay_get_for_controller(controller_id, relay_num); - if(action_relay == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(new_macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the relay for at least one action was not found"); - return; - } - - int action_relay_id = action_relay->id; - relay_free(action_relay); - - - macro_action_t *new_action = malloc(sizeof(macro_action_t)); - new_action->macro_id = new_macro->id; - new_action->relay_id = action_relay_id; - new_action->schedule_id = action_schedule_id; - new_action->weekday = json_action_weekday->valueint; - - macro_action_insert(new_action); - free(new_action); - } - - - - database_transaction_commit(&lock); - - cJSON_Delete(json); - json = macro_to_json(new_macro); - - endpoint_response_json(response, 201, json); - - cJSON_Delete(json); - macro_free(new_macro); -} - diff --git a/src/endpoints/api_v1_macros_STR.c b/src/endpoints/api_v1_macros_STR.c deleted file mode 100644 index 5c4c5fb..0000000 --- a/src/endpoints/api_v1_macros_STR.c +++ /dev/null @@ -1,280 +0,0 @@ -#include <cJSON.h> -#include <macros.h> -#include <constants.h> -#include <endpoints/api_v1_macros.h> -#include <logger.h> -#include <command.h> -#include <models/macro_action.h> -#include <models/macro.h> -#include <models/relay.h> -#include <models/tag.h> - -void -api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)hm; - (void)nc; - - uuid_t target_uid; - if(uuid_parse(args[0].value.v_str, target_uid)) - { - M_RESPONSE_400_NO_VALID_ID(response); - return; - } - - macro_t* macro = macro_get_by_uid(target_uid); - - if(!macro) - { - M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response); - return; - } - - cJSON *json = macro_to_json(macro); - - endpoint_response_json(response, 200, json); - cJSON_Delete(json); - macro_free(macro); -} - -void -api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)hm; - (void)nc; - - uuid_t target_uid; - if(uuid_parse(args[0].value.v_str, target_uid)) - { - M_RESPONSE_400_NO_VALID_ID(response); - return; - } - - macro_t* macro = macro_get_by_uid(target_uid); - - if(!macro) - { - M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response); - return; - } - - cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len); - - if(json == NULL) - { - M_RESPONSE_400_NO_VALID_JSON(response); - return; - } - - database_transaction_lock lock; - database_transaction_begin(&lock); - - cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name"); - if(cJSON_IsString(json_name) && (json_name->valuestring != NULL)) - { - strncpy(macro->name, json_name->valuestring, MAX_NAME_LENGTH); - macro->name[MAX_NAME_LENGTH] = '\0'; - - if(macro_save(macro)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); - return; - } - } - - macro_action_delete_for_macro(macro->id); - - cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions"); - if(cJSON_IsArray(json_actions)) - { - cJSON *json_action; - cJSON_ArrayForEach(json_action, json_actions) - { - cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday"); - if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a weekday"); - return; - } - - cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule"); - if(!cJSON_IsObject(json_action_schedule)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule"); - return; - } - cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id"); - if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule id"); - return; - } - uuid_t action_schedule_uid; - if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad schedule id"); - return; - } - - schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid); - if(action_schedule == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action schedule was not found"); - return; - } - - int action_schedule_id = action_schedule->id; - schedule_free(action_schedule); - - - cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay"); - if(!cJSON_IsObject(json_action_relay)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay"); - return; - } - cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number"); - if(!cJSON_IsNumber(json_action_relay_number)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay number"); - return; - } - cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id"); - if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay controller id"); - return; - } - uuid_t action_controller_uid; - if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid)) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad relay controller id"); - return; - } - - controller_t *action_controller = controller_get_by_uid(action_controller_uid); - if(action_controller == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action relay controller was not found"); - return; - } - - int controller_id = action_controller->id; - int relay_num = json_action_relay_number->valueint; - - controller_free(action_controller); - - relay_t *action_relay = relay_get_for_controller(controller_id, relay_num); - if(action_relay == NULL) - { - database_transaction_rollback(&lock); - cJSON_Delete(json); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "action relay was not found"); - return; - } - - int action_relay_id = action_relay->id; - relay_free(action_relay); - - - macro_action_t *action = malloc(sizeof(macro_action_t)); - action->macro_id = macro->id; - action->relay_id = action_relay_id; - action->schedule_id = action_schedule_id; - action->weekday = json_action_weekday->valueint; - - macro_action_insert(action); - free(action); - } - } - - database_transaction_commit(&lock); - - cJSON_Delete(json); - json = macro_to_json(macro); - - endpoint_response_json(response, 201, json); - - cJSON_Delete(json); - macro_free(macro); -} - -void -api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)hm; - (void)nc; - - const char *target_uid_str = args[0].value.v_str; - - uuid_t target_uid; - if(uuid_parse(target_uid_str, target_uid)) - { - M_RESPONSE_400_NO_VALID_ID(response); - return; - } - - macro_t* macro = macro_get_by_uid(target_uid); - - if(!macro) - { - M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response); - return; - } - - if(macro_remove(macro)) - { - M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response); - } - else - { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted macro"); - } - macro_free(macro); -} diff --git a/src/endpoints/api_v1_macros_STR_execute.c b/src/endpoints/api_v1_macros_STR_execute.c deleted file mode 100644 index 5097d85..0000000 --- a/src/endpoints/api_v1_macros_STR_execute.c +++ /dev/null @@ -1,85 +0,0 @@ -#include <cJSON.h> -#include <macros.h> -#include <constants.h> -#include <endpoints/api_v1_macros.h> -#include <logger.h> -#include <command.h> -#include <models/macro_action.h> -#include <models/macro.h> -#include <models/relay.h> -#include <models/tag.h> - -void -api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response) -{ - (void)nc; - - uuid_t target_uid; - if(uuid_parse(args[0].value.v_str, target_uid)) - { - M_RESPONSE_400_NO_VALID_ID(response); - return; - } - - macro_t* macro = macro_get_by_uid(target_uid); - - if(!macro) - { - M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response); - return; - } - - char *weekday_str = find_query_param(hm->query_string, "weekday"); - - macro_action_t** macro_actions; - - if (weekday_str != NULL) { - errno = 0; - char *end; - long weekday = strtol(weekday_str, &end, 10); - bool weekday_str_is_end = weekday_str == end; - - free(weekday_str); - - if (errno != 0 || weekday_str_is_end || weekday < 0 || weekday >= 7) { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the query contains an invalid weekday"); - return; - } - - macro_actions = macro_action_get_for_macro_and_weekday(macro->id, weekday); - } - else { - macro_actions = macro_action_get_for_macro(macro->id); - } - - - database_transaction_lock lock; - database_transaction_begin(&lock); - - for(int i = 0; macro_actions[i] != NULL; ++i) - { - macro_action_execute(macro_actions[i]); - } - - database_transaction_commit(&lock); - - int *target_relay_ids = macro_get_target_relay_ids(macro->id); - for(int i = 0; target_relay_ids[i] != 0; ++i) - { - relay_t *target_relay = relay_get_by_id(target_relay_ids[i]); - if(!target_relay) - { - LOGGER_ERR("failed to load target relay from database\n"); - continue; - } - command_relay_schedules_set(target_relay); - - relay_free(target_relay); - } - - free(target_relay_ids); - macro_action_free_list(macro_actions); - macro_free(macro); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "macro got executed"); -} diff --git a/src/endpoints/api_v1_relays_tag_STR.c b/src/endpoints/api_v1_relays_tag_STR.c index ffce429..06a6adc 100644 --- a/src/endpoints/api_v1_relays_tag_STR.c +++ b/src/endpoints/api_v1_relays_tag_STR.c @@ -16,13 +16,17 @@ api_v1_relays_tag_STR_GET(struct mg_connection *nc, struct http_message *hm, end int tag_id = tag_get_id(args[0].value.v_str); if(tag_id == 0) { - M_RESPONSE_404_NO_TAG_FOUND(response); + static const char content[] = "tag was not found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id); if(relays_ids == NULL) { - M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response); + LOGGER_ERR("failed to load relays for tag from database\n"); + + static const char content[] = "failed to load relays for tag from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } diff --git a/src/endpoints/api_v1_schedules.c b/src/endpoints/api_v1_schedules.c index 0b1599d..2dad24d 100644 --- a/src/endpoints/api_v1_schedules.c +++ b/src/endpoints/api_v1_schedules.c @@ -16,25 +16,29 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin if(json == NULL) { - M_RESPONSE_400_NO_VALID_JSON(response); + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name"); if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL)) { + LOGGER_DEBUG("no name for schedule provided\n"); cJSON_Delete(json); - M_RESPONSE_400_NO_NAME(response); + static const char content[] = "no name for schedule provided"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } - cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods"); if(!cJSON_IsArray(json_periods)) { + LOGGER_DEBUG("no periods for schedule provided\n"); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods"); + static const char content[] = "no periods for schedule provided"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -44,9 +48,11 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin { if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL)) { + LOGGER_DEBUG("invalid tag in tags\n"); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag"); + static const char content[] = "invalid tag in tags"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } } @@ -72,18 +78,22 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL)) { + LOGGER_DEBUG("period is missing start\n"); cJSON_Delete(json); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start"); + static const char content[] = "one period is missing a start"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL)) { + LOGGER_DEBUG("period is missing end\n"); cJSON_Delete(json); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end"); + static const char content[] = "one period is missing an end"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -91,18 +101,22 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin uint16_t end; if(period_helper_parse_hhmm(json_period_start->valuestring, &start)) { + LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring); cJSON_Delete(json); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start"); + static const char content[] = "the start for one period is invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(period_helper_parse_hhmm(json_period_end->valuestring, &end)) { + LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring); cJSON_Delete(json); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end"); + static const char content[] = "the end for one period is invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } diff --git a/src/endpoints/api_v1_schedules_STR.c b/src/endpoints/api_v1_schedules_STR.c index 5397e49..03613d1 100644 --- a/src/endpoints/api_v1_schedules_STR.c +++ b/src/endpoints/api_v1_schedules_STR.c @@ -19,7 +19,10 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp uuid_t target_uid; if(schedule_uid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -27,7 +30,10 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp if(!schedule) { - M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no schedule for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } @@ -47,7 +53,10 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp uuid_t target_uid; if(schedule_uid_parse(args[0].value.v_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -55,7 +64,10 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp if(!schedule) { - M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no schedule for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } @@ -63,7 +75,8 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp if(json == NULL) { - M_RESPONSE_400_NO_VALID_JSON(response); + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -124,10 +137,12 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp if(schedule_save(schedule)) { + LOGGER_ERR("failed to save schedule\n"); free(schedule); cJSON_Delete(json); - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); + static const char content[] = "failed to save schedule to database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } @@ -175,7 +190,10 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e uuid_t target_uid; if(schedule_uid_parse(target_uid_str, target_uid)) { - M_RESPONSE_400_NO_VALID_ID(response); + LOGGER_DEBUG("failed to unparse uid\n"); + + static const char content[] = "given id was invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -183,25 +201,33 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e if(!schedule) { - M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response); + LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str); + + static const char content[] = "no schedule for id found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } if(schedule_is_protected(schedule)) { - schedule_free(schedule); + static const char content[] = "target schedule is protected"; + endpoint_response_text(response, 403, content, STRLEN(content)); - M_RESPONSE_403_PROTECTED_SCHEDULE(response); + schedule_free(schedule); return; } if(schedule_remove(schedule)) { - M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response); + LOGGER_ERR("failed to remove schedule from database\n"); + + static const char content[] = "failed to remove schedule from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); } else { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the target schedule got deleted"); + endpoint_response_text(response, 200, "", 0); } schedule_free(schedule); + return; } diff --git a/src/endpoints/api_v1_schedules_list.c b/src/endpoints/api_v1_schedules_list.c index d9be32a..ede980b 100644 --- a/src/endpoints/api_v1_schedules_list.c +++ b/src/endpoints/api_v1_schedules_list.c @@ -20,23 +20,29 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en { if(json == NULL) { - M_RESPONSE_400_NO_VALID_JSON(response); + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name"); if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL)) { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name for at least one schedule"); + LOGGER_DEBUG("no name for schedule provided\n"); + cJSON_Delete(json_list); + + static const char content[] = "no name for schedule provided"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } - cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods"); if(!cJSON_IsArray(json_periods)) { + LOGGER_DEBUG("no periods for schedule provided\n"); cJSON_Delete(json_list); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods for at least one schedule"); + static const char content[] = "no periods for schedule provided"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -46,9 +52,11 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en { if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL)) { + LOGGER_DEBUG("invalid tag in tags\n"); cJSON_Delete(json_list); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag"); + static const char content[] = "invalid tag in tags"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } } @@ -74,18 +82,22 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL)) { + LOGGER_DEBUG("period is missing start\n"); cJSON_Delete(json_list); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start"); + static const char content[] = "one period is missing a start"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL)) { + LOGGER_DEBUG("period is missing end\n"); cJSON_Delete(json_list); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end"); + static const char content[] = "one period is missing an end"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } @@ -93,18 +105,22 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en uint16_t end; if(period_helper_parse_hhmm(json_period_start->valuestring, &start)) { + LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring); cJSON_Delete(json_list); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start"); + static const char content[] = "the start for one period is invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(period_helper_parse_hhmm(json_period_end->valuestring, &end)) { + LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring); cJSON_Delete(json_list); schedule_free(new_schedule); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end"); + static const char content[] = "the end for one period is invalid"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } diff --git a/src/endpoints/api_v1_schedules_tag_STR.c b/src/endpoints/api_v1_schedules_tag_STR.c index 08723dc..b0d22ec 100644 --- a/src/endpoints/api_v1_schedules_tag_STR.c +++ b/src/endpoints/api_v1_schedules_tag_STR.c @@ -16,13 +16,17 @@ api_v1_schedules_tag_STR_GET(struct mg_connection *nc, struct http_message *hm, int tag_id = tag_get_id(args[0].value.v_str); if(tag_id == 0) { - M_RESPONSE_404_NO_TAG_FOUND(response); + static const char content[] = "tag was not found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id); if(schedules_ids == NULL) { - M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response); + LOGGER_ERR("failed to load schedules for tag from database\n"); + + static const char content[] = "failed to load schedules for tag from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } diff --git a/src/endpoints/api_v1_tags.c b/src/endpoints/api_v1_tags.c index 5ab74c5..2bd4afa 100644 --- a/src/endpoints/api_v1_tags.c +++ b/src/endpoints/api_v1_tags.c @@ -44,54 +44,56 @@ api_v1_tags_POST(struct mg_connection *nc, struct http_message *hm, endpoint_arg if(json == NULL) { - M_RESPONSE_400_NO_VALID_JSON(response); + static const char content[] = "no valid json was supplied"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } cJSON *json_tag = cJSON_GetObjectItemCaseSensitive(json, "tag"); if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL)) { + LOGGER_DEBUG("no tag provided\n"); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a tag"); - return; - } - - if(strlen(json_tag->valuestring) == 0) - { - cJSON_Delete(json); - - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an empty tag"); + static const char content[] = "no tag provided"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(tag_get_id(json_tag->valuestring)) { + LOGGER_DEBUG("tag existed already\n"); cJSON_Delete(json); - M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the tag does already exist"); + static const char content[] = "tag existed already"; + endpoint_response_text(response, 400, content, STRLEN(content)); + return; + } + + if(strlen(json_tag->valuestring) == 0) + { + LOGGER_DEBUG("tag is empty\n"); + cJSON_Delete(json); + + static const char content[] = "tag is empty"; + endpoint_response_text(response, 400, content, STRLEN(content)); return; } if(tag_save(0, json_tag->valuestring)) { - M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response); + LOGGER_DEBUG("tag could not be saved\n"); + + static const char content[] = "tag could not be saved"; + endpoint_response_text(response, 500, content, STRLEN(content)); } else { LOGGER_DEBUG("new tag saved\n"); - size_t tag_len = strlen(json_tag->valuestring); - - // NOLINT(clang-analyzer-unix.Malloc): The endpoint response will be freed later. - char *tag = malloc(sizeof(char) * (tag_len + 1)); - strcpy(tag, json_tag->valuestring); - tag[tag_len] = '\0'; - - endpoint_response_text(response, 201, tag, 0); - - free(tag); + endpoint_response_text(response, 201, json_tag->valuestring, 0); } cJSON_Delete(json); + return; } diff --git a/src/endpoints/api_v1_tags_STR.c b/src/endpoints/api_v1_tags_STR.c index 6bf0d9c..c9f0426 100644 --- a/src/endpoints/api_v1_tags_STR.c +++ b/src/endpoints/api_v1_tags_STR.c @@ -16,19 +16,26 @@ api_v1_tags_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_ int tag_id = tag_get_id(args[0].value.v_str); if(tag_id == 0) { - M_RESPONSE_404_NO_TAG_FOUND(response); + static const char content[] = "tag was not found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id); if(relays_ids == NULL) { - M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response); + LOGGER_ERR("failed to load relays for tag from database\n"); + + static const char content[] = "failed to load relays for tag from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id); if(schedules_ids == NULL) { - M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response); + LOGGER_ERR("failed to load schedules for tag from database\n"); + + static const char content[] = "failed to load schedules for tag from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); return; } @@ -84,16 +91,20 @@ api_v1_tags_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoi int tag_id = tag_get_id(args[0].value.v_str); if(tag_id == 0) { - M_RESPONSE_404_NO_TAG_FOUND(response); + static const char content[] = "tag was not found"; + endpoint_response_text(response, 404, content, STRLEN(content)); return; } if(tag_remove(tag_id)) { - M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response); + LOGGER_ERR("failed to remove tag from database\n"); + + static const char content[] = "failed to remove tag from database"; + endpoint_response_text(response, 500, content, STRLEN(content)); } else { - M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the tag got deleted"); + endpoint_response_text(response, 200, "", 0); } } diff --git a/src/handlers/http.c b/src/handlers/http.c index b72db52..87d2908 100644 --- a/src/handlers/http.c +++ b/src/handlers/http.c @@ -17,17 +17,16 @@ static char* add_extra_headers(char *extra_headers) { char *result; - size_t std_headers_len = strlen(global_config->http_server_opts.extra_headers); + size_t std_headers_len = strlen(global_config.http_server_opts.extra_headers); if(extra_headers == NULL) { result = malloc(sizeof(char) * (std_headers_len + 1)); - strcpy(result, global_config->http_server_opts.extra_headers); - result[std_headers_len] = '\0'; + strcpy(result, global_config.http_server_opts.extra_headers); return result; } result = malloc(sizeof(char) * (std_headers_len + strlen(extra_headers) + 3)); - sprintf(result, "%s\r\n%s", global_config->http_server_opts.extra_headers, extra_headers); + sprintf(result, "%s\r\n%s", global_config.http_server_opts.extra_headers, extra_headers); return result; } @@ -45,6 +44,11 @@ send_response(struct mg_connection *nc, endpoint_response_t *response) free(response_headers); free(extra_headers); + + if(response->alloced_content) + { + free((char*)response->content); + } } } @@ -78,18 +82,15 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm) endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method); endpoint_response_t response; - response.content = NULL; - response.alloced_content = false; - - M_RESPONSE_MSG(LOGGER_NONE, &response, 500, "server did not create a response"); + static const char content[] = "the server did not create a response"; + endpoint_response_text(&response, 500, content, STRLEN(content)); if(!endpoint) { /* Normalize path - resolve "." and ".." (in-place). */ if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) { - mg_http_send_error(nc, 400, global_config->http_server_opts.extra_headers); + mg_http_send_error(nc, 400, global_config.http_server_opts.extra_headers); LOGGER_DEBUG("failed to normalize uri %.*s\n", hm->uri.len, hm->uri.p); - endpoint_response_free_content(&response); return; } LOGGER_DEBUG("no endpoint found - serving file\n"); @@ -104,22 +105,23 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm) ++request_file; } - char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config->content_dir) + 2)); - sprintf(request_file_path, "%s/%s", global_config->content_dir, request_file); + char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config.content_dir) + 2)); + sprintf(request_file_path, "%s/%s", global_config.content_dir, request_file); int access_result = access(request_file_path, R_OK); free(request_file_path); free(request_file_org); if(access_result != -1) { response.status_code = 0; - mg_serve_http(nc, hm, global_config->http_server_opts); + mg_serve_http(nc, hm, global_config.http_server_opts); LOGGER_DEBUG("serving %.*s\n", hm->uri.len, hm->uri.p); - endpoint_response_free_content(&response); return; } - - LOGGER_DEBUG("serving 'not found'\n"); - endpoint = router_get_not_found_endpoint(); + else + { + LOGGER_DEBUG("serving 'not found'\n"); + endpoint = router_get_not_found_endpoint(); + } } if(endpoint->method == HTTP_METHOD_OPTIONS) @@ -146,7 +148,6 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm) send_response(nc, &response); } - endpoint_response_free_content(&response); LOGGER_DEBUG("freeing endpoint args\n"); for(int i = 0; i < endpoint->args_count; ++i) { diff --git a/src/handlers/mqtt.c b/src/handlers/mqtt.c index 886e556..7f26ffc 100644 --- a/src/handlers/mqtt.c +++ b/src/handlers/mqtt.c @@ -9,7 +9,7 @@ #include <models/relay.h> static void -handle_mqtt_publish_controller(char **topic_save, int controller_id, const char *payload) +handle_mqtt_publish_controller(char **topic_save, int controller_id, char *payload) { (void)controller_id; (void)payload; diff --git a/src/helpers/connect_server.c b/src/helpers/connect_server.c index 7f01a74..dbfdc80 100644 --- a/src/helpers/connect_server.c +++ b/src/helpers/connect_server.c @@ -13,11 +13,8 @@ helper_connect_tcp_server(char* host, uint16_t port) char port_str[6]; sprintf(port_str, "%d", port); - int s; - int status; - struct addrinfo *res; - struct addrinfo hints; - + int s, status; + struct addrinfo hints, *res; memset(&hints, 0, sizeof hints); hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care) hints.ai_socktype = SOCK_STREAM; //set socket flag @@ -30,7 +27,7 @@ helper_connect_tcp_server(char* host, uint16_t port) //res got filled out by getaddrinfo() for us s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket - if (connect(s, res->ai_addr, res->ai_addrlen) != 0) { + if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) { LOGGER_ERR("connect() failed\n"); freeaddrinfo(res); return -1; diff --git a/src/helpers/drop_privileges.c b/src/helpers/drop_privileges.c index ff95f63..e2dde8a 100644 --- a/src/helpers/drop_privileges.c +++ b/src/helpers/drop_privileges.c @@ -15,20 +15,21 @@ get_uid_for_user(char *user) { return getuid(); } - struct passwd pwd; - struct passwd *result = NULL; + struct passwd *pwd = calloc(1, sizeof(struct passwd)); size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char); char *buffer = malloc(buffer_len); - getpwnam_r(user, &pwd, buffer, buffer_len, &result); + getpwnam_r(user, pwd, buffer, buffer_len, &pwd); - if(result == NULL) + if(pwd == NULL) { LOGGER_CRIT("couldn't find user to drop privileges\n"); exit(1); } + uid_t result = pwd->pw_uid; free(buffer); - return result->pw_uid; + free(pwd); + return result; } static gid_t @@ -38,27 +39,28 @@ get_gid_for_group(char *group) { return getgid(); } - struct group grp; - struct group *result = NULL; + struct group *grp = calloc(1, sizeof(struct group)); size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char); char *buffer = malloc(buffer_len); - getgrnam_r(group, &grp, buffer, buffer_len, &result); + getgrnam_r(group, grp, buffer, buffer_len, &grp); - if(result == NULL) + if(grp == NULL) { LOGGER_CRIT("couldn't find group to drop privileges\n"); exit(1); } + gid_t result = grp->gr_gid; free(buffer); - return result->gr_gid; + free(grp); + return result; } int helper_drop_privileges() { - uid_t uid = get_uid_for_user(global_config->user); - gid_t gid = get_gid_for_group(global_config->group); + uid_t uid = get_uid_for_user(global_config.user); + gid_t gid = get_gid_for_group(global_config.group); LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid); diff --git a/src/helpers/find_query_param.c b/src/helpers/find_query_param.c deleted file mode 100644 index 3ae6246..0000000 --- a/src/helpers/find_query_param.c +++ /dev/null @@ -1,37 +0,0 @@ -#include <string.h> - -#include <mongoose.h> - -char* -find_query_param(struct mg_str query_mg_str, char* search_key) -{ - if (query_mg_str.len == 0) { - return NULL; - } - - // Convert query string to null-terminated string - char* query = malloc(sizeof(char) * (query_mg_str.len + 1)); - strncpy(query, query_mg_str.p, query_mg_str.len); - query[query_mg_str.len] = '\0'; - - char* result = NULL; - - // Tokenize query string - char *token = strtok(query, "&"); - while (token != NULL) { - char *key = strtok(token, "="); - char *val = strtok(NULL, "="); - if (key != NULL && val != NULL) { - if (strcmp(key, search_key) == 0) { - size_t val_len = strlen(val); - result = malloc(sizeof(char) * (val_len + 1)); - strncpy(result, val, val_len + 1); - result[val_len] = '\0'; // Ensure null termination - break; - } - } - token = strtok(NULL, "&"); - } - free(query); - return result; -} \ No newline at end of file diff --git a/src/helpers/get_weekday.c b/src/helpers/get_weekday.c index fb8be62..68f029e 100644 --- a/src/helpers/get_weekday.c +++ b/src/helpers/get_weekday.c @@ -1,12 +1,11 @@ #include <time.h> -#include <constants.h> #include <helpers.h> int helper_get_weekday(const struct tm *time_struct) { int wday_sun_sat = time_struct->tm_wday; - int wday_mon_sun = (wday_sun_sat + (DAYS_PER_WEEK - 1)) % DAYS_PER_WEEK; + int wday_mon_sun = (wday_sun_sat + 6) % 7; return wday_mon_sun; } diff --git a/src/cli.c b/src/helpers/parse_cli.c similarity index 58% rename from src/cli.c rename to src/helpers/parse_cli.c index 2719aa1..f205541 100644 --- a/src/cli.c +++ b/src/helpers/parse_cli.c @@ -5,29 +5,25 @@ #include <argparse.h> #include <config.h> -#include <cli.h> #include <logger.h> #include <helpers.h> #include <version.h> -cli_t *global_cli; - static const char *const usage[] = { + "core [options] [[--] args]", "core [options]", NULL, }; void -cli_parse(int argc, const char **argv, cli_t *cli) +helper_parse_cli(int argc, const char **argv, config_t *config) { - cli->config_file = NULL; - int version = 0; struct argparse_option options[] = { OPT_HELP(), OPT_GROUP("Basic options"), - OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG), + OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG), OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG), OPT_END(), }; @@ -39,11 +35,29 @@ cli_parse(int argc, const char **argv, cli_t *cli) "\nA brief description of what the program does and how it works.", "\nAdditional description of the program after the description of the arguments." ); - argparse_parse(&argparse, argc, argv); + argc = argparse_parse(&argparse, argc, argv); if(version) { printf("%s\n", EMGAUWA_CORE_VERSION); exit(0); } + + if(argc == 1) + { + config->run_type = RUN_TYPE_INVALID; + if(strcmp(argv[0], "start") == 0) + { + config->run_type = RUN_TYPE_START; + return; + } + LOGGER_CRIT("bad action '%s' given ('start')\n", argv[0]); + exit(1); + } + else + { + LOGGER_CRIT("no action given ('start')\n"); + exit(1); + } + return; } diff --git a/src/logger.c b/src/logger.c index 8ba8af2..2869668 100644 --- a/src/logger.c +++ b/src/logger.c @@ -17,11 +17,10 @@ const char *COLOR_EMERG = COLOR_MAGENTA; void logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...) { - if(global_config->logging.level < level || level == LOG_NONE ) + if(global_config.log_level < level) { return; } - va_list args; const char *level_str; const char *color; @@ -63,34 +62,24 @@ logger_log(int level, const char *filename, int line, const char *func, const ch time_t rawtime; time(&rawtime); strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime)); - size_t timestamp_len = strlen(timestamp_str); - size_t buffer_size = 128; - buffer_size += timestamp_len; - buffer_size += strlen(filename); - buffer_size += strlen(func); - buffer_size += strlen(msg); + char *buffer = malloc(sizeof(char) * (128 + strlen(msg))); + sprintf(buffer, "%s[%5s] %s:%d:%s " COLOR_NONE "%s", color, level_str, filename, line, func, msg); - char *buffer = malloc(sizeof(char) * (buffer_size)); - sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg); + //fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func); - // start arg va_list and find log_len + va_list args; va_start(args, msg); - size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug + vsyslog(level, buffer, args); va_end(args); - char *log_line = malloc(sizeof(char) * (log_len + 1)); - - // start arg va_list again and write log_line + char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2)); + sprintf(buffer_timed, "%s %s", timestamp_str, buffer); va_start(args, msg); - vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug + vfprintf(global_config.log_file, buffer_timed, args); + fflush(global_config.log_file); va_end(args); - syslog(level, "%s", log_line + timestamp_len + 1); - - fprintf(global_config->logging.file, "%s", log_line); - fflush(global_config->logging.file); - free(buffer); - free(log_line); + free(buffer_timed); } diff --git a/src/main.c b/src/main.c index fdfe01a..cc60f25 100644 --- a/src/main.c +++ b/src/main.c @@ -1,28 +1,28 @@ -#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <stdio.h> #include <syslog.h> #include <mongoose.h> +#include <confini.h> #include <cache.h> -#include <cli.h> +#include <router.h> +#include <logger.h> #include <config.h> #include <database.h> -#include <enums.h> #include <handlers.h> +#include <enums.h> #include <helpers.h> -#include <logger.h> -#include <models/controller.h> -#include <router.h> #include <status.h> +#include <models/controller.h> static struct mg_mgr mgr; static void terminate(int signum) { - LOGGER_INFO("terminating core (%d)\n", signum); + LOGGER_INFO("terminating controller (%d)\n", signum); mg_mgr_free(&mgr); @@ -30,7 +30,6 @@ terminate(int signum) router_free(); status_free(); cache_free(); - config_free(); closelog(); @@ -52,27 +51,54 @@ main(int argc, const char** argv) signal(SIGABRT, terminate); signal(SIGTERM, terminate); - openlog("emgauwa-core", 0, LOG_USER); - setlogmask(LOG_UPTO(LOG_DEBUG)); + setlogmask(LOG_UPTO(LOG_INFO)); + /******************** LOAD CONFIG ********************/ - config_init(); + global_config.file = "core.ini"; + global_config.discovery_port = 4421; + global_config.mqtt_port = 1885; + global_config.server_port = 5000; - cli_t cli; - cli_parse(argc, argv, &cli); + global_config.log_level = LOG_INFO; + global_config.log_file = stdout; - config_load(global_config, cli.config_file); + strcpy(global_config.user, ""); + strcpy(global_config.group, ""); - if(global_config->logging.file == NULL) + strcpy(global_config.content_dir, "."); + strcpy(global_config.not_found_file, "404.html"); + strcpy(global_config.not_found_file_type, "text/html"); + strcpy(global_config.not_found_content, "404 - NOT FOUND"); + strcpy(global_config.not_found_content_type, "text/plain"); + + helper_parse_cli(argc, argv, &global_config); + + FILE * const ini_file = fopen(global_config.file, "rb"); + if(ini_file == NULL) { - global_config->logging.file = stdout; + LOGGER_CRIT("config file '%s' was not found\n", global_config.file); + exit(1); + } + if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, config_load, &global_config)) + { + LOGGER_CRIT("unable to parse ini file\n"); + exit(1); } - if(global_config->include) + fclose(ini_file); + + memset(&global_config.http_server_opts, 0, sizeof(global_config.http_server_opts)); + global_config.http_server_opts.document_root = global_config.content_dir; + global_config.http_server_opts.enable_directory_listing = "no"; + global_config.http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *"; + + if(global_config.log_file == NULL) { - config_load_directory(global_config, global_config->include); + global_config.log_file = stdout; } + openlog("emgauwa-core", 0, LOG_USER); /******************** SETUP CONNECTION ********************/ @@ -82,20 +108,20 @@ main(int argc, const char** argv) mg_mgr_init(&mgr, NULL); char address[100]; - sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.server); + sprintf(address, "tcp://0.0.0.0:%u", global_config.server_port); struct mg_connection *c_http = mg_bind(&mgr, address, handler_http); if(c_http == NULL) { - LOGGER_CRIT("failed to bind http server to port %u\n", global_config->ports.server); + LOGGER_CRIT("failed to bind http server to port %u\n", global_config.server_port); exit(1); } mg_set_protocol_http_websocket(c_http); - sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.mqtt); + sprintf(address, "tcp://0.0.0.0:%u", global_config.mqtt_port); struct mg_connection *c_mqtt = mg_bind(&mgr, address, handler_mqtt); if(c_mqtt == NULL) { - LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config->ports.mqtt); + LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config.mqtt_port); exit(1); } mg_mqtt_broker_init(&brk, NULL); @@ -104,11 +130,6 @@ main(int argc, const char** argv) helper_drop_privileges(); - memset(&global_config->http_server_opts, 0, sizeof(global_config->http_server_opts)); - global_config->http_server_opts.document_root = global_config->content_dir; - global_config->http_server_opts.enable_directory_listing = "no"; - global_config->http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *"; - /******************** INIT COMPONENTS ********************/ diff --git a/src/models/controller.c b/src/models/controller.c index 3ed6048..807853c 100644 --- a/src/models/controller.c +++ b/src/models/controller.c @@ -35,8 +35,6 @@ static controller_t* controller_db_select_mapper(sqlite3_stmt *stmt) { controller_t *new_controller = malloc(sizeof(controller_t)); - new_controller->id = 0; - for(int i = 0; i < sqlite3_column_count(stmt); i++) { const char *name = sqlite3_column_name(stmt, i); @@ -52,16 +50,14 @@ controller_db_select_mapper(sqlite3_stmt *stmt) new_controller->id = sqlite3_column_int(stmt, i); break; case 'p': // ip - strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), IP_LENGTH); - new_controller->ip[IP_LENGTH] = '\0'; + strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16); break; default: // ignore columns not implemented break; } break; case 'n': // name - strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH); - new_controller->name[MAX_NAME_LENGTH] = '\0'; + strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127); break; case 'p': // port new_controller->port = sqlite3_column_int(stmt, i); @@ -106,9 +102,11 @@ controller_db_select(sqlite3_stmt *stmt) { break; } - - LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s)); - break; + else + { + LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s)); + break; + } } } sqlite3_finalize(stmt); @@ -119,8 +117,7 @@ controller_db_select(sqlite3_stmt *stmt) int controller_save(controller_t *controller) { - database_transaction_lock lock; - database_transaction_begin(&lock); + int opened_transaction = database_transaction_begin(); sqlite3_stmt *stmt; if(controller->id) @@ -145,7 +142,10 @@ controller_save(controller_t *controller) LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database)); } - database_transaction_rollback(&lock); + if(opened_transaction) + { + database_transaction_rollback(); + } } else { @@ -154,7 +154,10 @@ controller_save(controller_t *controller) controller->id = sqlite3_last_insert_rowid(global_database); } - database_transaction_commit(&lock); + if(opened_transaction) + { + database_transaction_commit(); + } } cache_invalidate_controller(controller->id); diff --git a/src/models/junction_relay_schedule.c b/src/models/junction_relay_schedule.c index e2c8411..e2b2309 100644 --- a/src/models/junction_relay_schedule.c +++ b/src/models/junction_relay_schedule.c @@ -40,16 +40,14 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids) static const char query_base[] = "INSERT INTO junction_relay_schedule (weekday, schedule_id, relay_id) VALUES"; static const char query_extender[] = " (?, ?, ?)"; - size_t query_len = M_STRLEN(query_base) + (7 * (M_STRLEN(query_extender) + 1)) + 1; + size_t query_len = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1; char *query = malloc(sizeof(char) * query_len + 1); strncpy(query, query_base, query_len); - query[query_len] = '\0'; - - query_len -= M_STRLEN(query_base); + query_len -= STRLEN(query_base); for(int i = 0; i < 7; ++i) { strncat(query, query_extender, query_len); - query_len -= M_STRLEN(query_extender); + query_len -= STRLEN(query_extender); char *query_divider = (i < 7 - 1) ? "," : ";"; strncat(query, query_divider, query_len); query_len -= 1; @@ -64,18 +62,16 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids) sqlite3_bind_int(stmt, i * 3 + 3, relay_id); } - free(query); - rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database)); - return 1; + return false; } sqlite3_finalize(stmt); - return 0; + return true; } int @@ -91,14 +87,3 @@ junction_relay_schedule_remove_for_relay(int relay_id) return rc == SQLITE_DONE; } - -int* -junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, schedule_id); - - return database_helper_get_ids(stmt); -} diff --git a/src/models/junction_tag.c b/src/models/junction_tag.c index 10700ad..a62bed8 100644 --- a/src/models/junction_tag.c +++ b/src/models/junction_tag.c @@ -67,16 +67,14 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count) static const char query_base[] = "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) VALUES"; static const char query_extender[] = " (?, ?, ?)"; - size_t query_len = M_STRLEN(query_base) + (count * (M_STRLEN(query_extender) + 1)) + 1; - char *query = malloc(sizeof(char) * (query_len + 1)); + size_t query_len = STRLEN(query_base) + (count * (STRLEN(query_extender) + 1)) + 1; + char *query = malloc(sizeof(char) * query_len + 1); strncpy(query, query_base, query_len); - query[query_len] = '\0'; - - query_len -= M_STRLEN(query_base); + query_len -= STRLEN(query_base); for(int i = 0; i < count; ++i) { strncat(query, query_extender, query_len); - query_len -= M_STRLEN(query_extender); + query_len -= STRLEN(query_extender); char *query_divider = (i < count - 1) ? "," : ";"; strncat(query, query_divider, query_len); query_len -= 1; @@ -107,8 +105,6 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count) } } - free(query); - rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { diff --git a/src/models/macro.c b/src/models/macro.c deleted file mode 100644 index 00a72aa..0000000 --- a/src/models/macro.c +++ /dev/null @@ -1,328 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <sqlite3.h> - -#include <cache.h> -#include <cJSON.h> -#include <logger.h> -#include <database.h> -#include <models/macro.h> -#include <models/macro_action.h> -#include <models/schedule.h> -#include <models/tag.h> - -static int -db_update_insert(macro_t *macro, sqlite3_stmt *stmt) -{ - LOGGER_DEBUG("saving macro '%s' into database (id: %d)\n", macro->name, macro->id); - - int rc; - - sqlite3_bind_int(stmt, 1, macro->id); - sqlite3_bind_blob(stmt, 2, macro->uid, sizeof(uuid_t), SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, macro->name, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - - sqlite3_finalize(stmt); - - return rc != SQLITE_DONE; -} - -static macro_t* -macro_db_select_mapper(sqlite3_stmt *stmt) -{ - macro_t *new_macro = malloc(sizeof(macro_t)); - for(int i = 0; i < sqlite3_column_count(stmt); i++) - { - const char *name = sqlite3_column_name(stmt, i); - switch(name[0]) - { - case 'i': // id - new_macro->id = sqlite3_column_int(stmt, i); - break; - case 'n': // name - strncpy(new_macro->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH); - new_macro->name[MAX_NAME_LENGTH] = '\0'; - break; - case 'u': // uid - uuid_copy(new_macro->uid, (const unsigned char*)sqlite3_column_blob(stmt, i)); - break; - default: // ignore columns not implemented - break; - } - } - return new_macro; -} - -static macro_t** -macro_db_select(sqlite3_stmt *stmt) -{ - macro_t **all_macros = malloc(sizeof(macro_t*)); - - int row = 0; - - for(;;) - { - int s; - - s = sqlite3_step(stmt); - if (s == SQLITE_ROW) - { - macro_t *new_macro = macro_db_select_mapper(stmt); - row++; - - all_macros = (macro_t**)realloc(all_macros, sizeof(macro_t*) * (row + 1)); - all_macros[row - 1] = new_macro; - } - else - { - if(s == SQLITE_DONE) - { - break; - } - - LOGGER_ERR("error selecting macros from database: %s\n", sqlite3_errstr(s)); - break; - } - } - sqlite3_finalize(stmt); - all_macros[row] = NULL; - return all_macros; -} - -int -macro_save(macro_t *macro) -{ - database_transaction_lock lock; - database_transaction_begin(&lock); - - sqlite3_stmt *stmt; - if(macro->id) - { - sqlite3_prepare_v2(global_database, "UPDATE macros SET uid = ?2, name = ?3 WHERE id=?1;", -1, &stmt, NULL); - } - else - { - sqlite3_prepare_v2(global_database, "INSERT INTO macros(uid, name) values (?2, ?3);", -1, &stmt, NULL); - } - - int result = db_update_insert(macro, stmt); - - if(result) - { - if(macro->id) - { - LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database)); - } - else - { - LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database)); - } - - database_transaction_rollback(&lock); - } - else - { - if(!macro->id) - { - macro->id = sqlite3_last_insert_rowid(global_database); - } - - database_transaction_commit(&lock); - } - - cache_invalidate_macro(macro->id); - - return result; -} - -int -macro_remove(macro_t *macro) -{ - sqlite3_stmt *stmt; - if(macro->id == 0) - { - return 0; - } - - sqlite3_prepare_v2(global_database, "DELETE FROM macros WHERE id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, macro->id); - - int rc = sqlite3_step(stmt); - - sqlite3_finalize(stmt); - - cache_invalidate_macro(macro->id); - - return rc != SQLITE_DONE; -} - -void -macro_free(macro_t *macro) -{ - free(macro); -} - -void -macro_free_list(macro_t **macros) -{ - for(int i = 0; macros[i] != NULL; ++i) - { - macro_free(macros[i]); - } - free(macros); -} - -cJSON* -macro_to_json(macro_t *macro) -{ - cJSON *json; - - char *cached = cache_get_json_macro(macro->id); - if(cached) - { - json = cJSON_CreateRaw(cached); - free(cached); - return json; - } - - char uuid_str[UUID_STR_LEN]; - uuid_unparse(macro->uid, uuid_str); - - LOGGER_DEBUG("JSONifying macro %s\n", uuid_str); - - json = cJSON_CreateObject(); - - cJSON *json_name = cJSON_CreateString(macro->name); - if(json_name == NULL) - { - cJSON_Delete(json); - return NULL; - } - cJSON_AddItemToObject(json, "name", json_name); - - cJSON *json_id = cJSON_CreateString(uuid_str); - if(json_name == NULL) - { - cJSON_Delete(json); - return NULL; - } - cJSON_AddItemToObject(json, "id", json_id); - - cJSON *json_actions = cJSON_CreateArray(); - macro_action_t **macro_actions = macro_action_get_for_macro(macro->id); - for(int i = 0; macro_actions[i] != NULL; ++i) - { - cJSON *json_action = cJSON_CreateObject(); - - cJSON *json_action_weekday = cJSON_CreateNumber(macro_actions[i]->weekday); - if(json_action_weekday == NULL) - { - LOGGER_DEBUG("failed to create weekday from number %d\n", macro_actions[i]->weekday); - continue; - } - - relay_t *relay = relay_get_by_id(macro_actions[i]->relay_id); - if(!relay) - { - LOGGER_DEBUG("failed to get relay\n"); - continue; - } - schedule_t *schedule = schedule_get_by_id(macro_actions[i]->schedule_id); - if(!schedule) - { - LOGGER_DEBUG("failed to get schedule\n"); - relay_free(relay); - continue; - } - - cJSON_AddItemToObject(json_action, "weekday", json_action_weekday); - cJSON_AddItemToObject(json_action, "relay", relay_to_json(relay, 0)); - cJSON_AddItemToObject(json_action, "schedule", schedule_to_json(schedule)); - - cJSON_AddItemToArray(json_actions, json_action); - } - cJSON_AddItemToObject(json, "actions", json_actions); - - macro_action_free_list(macro_actions); - - char *json_str = cJSON_Print(json); - cache_put_json_macro(macro->id, json_str); - cJSON_Delete(json); - - json = cJSON_CreateRaw(json_str); - free(json_str); - return json; -} - -macro_t* -macro_get_by_id(int id) -{ - LOGGER_DEBUG("getting macro [id=%d] from database\n", id); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE id = ?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, id); - - macro_t **sql_result = macro_db_select(stmt); - - macro_t *result = sql_result[0]; - free(sql_result); - - return result; -} - -macro_t* -macro_get_by_uid(uuid_t uid) -{ - char uuid_str[UUID_STR_LEN]; - uuid_unparse(uid, uuid_str); - LOGGER_DEBUG("getting macro [uid=%s] from database\n", uuid_str); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE uid = ?1;", -1, &stmt, NULL); - sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC); - - macro_t **sql_result = macro_db_select(stmt); - - macro_t *result = sql_result[0]; - free(sql_result); - - return result; -} - -macro_t** -macro_get_relay_weekdays(int relay_id) -{ - LOGGER_DEBUG("getting macros [relay_id=%d] from database\n", relay_id); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT macros.* FROM macros INNER JOIN junction_relay_macro ON macros.id == junction_relay_macro.macro_id WHERE junction_relay_macro.relay_id = ?1 ORDER BY junction_relay_macro.weekday ASC", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, relay_id); - - return macro_db_select(stmt); -} - -macro_t** -macro_get_all() -{ - LOGGER_DEBUG("getting all macros from database\n"); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT * FROM macros;", -1, &stmt, NULL); - - return macro_db_select(stmt); - -} - -int* -macro_get_target_relay_ids(int macro_id) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, macro_id); - - return database_helper_get_ids(stmt); -} diff --git a/src/models/macro_action.c b/src/models/macro_action.c deleted file mode 100644 index e603c00..0000000 --- a/src/models/macro_action.c +++ /dev/null @@ -1,189 +0,0 @@ -#include <stdlib.h> -#include <stdint.h> -#include <stddef.h> - -#include <models/macro_action.h> -#include <logger.h> -#include <cache.h> -#include <database.h> - -static macro_action_t* -macro_action_db_select_mapper(sqlite3_stmt *stmt) -{ - macro_action_t *new_macro_action = malloc(sizeof(macro_action_t)); - for(int i = 0; i < sqlite3_column_count(stmt); i++) - { - const char *name = sqlite3_column_name(stmt, i); - switch(name[0]) - { - case 'm': // macro_id - new_macro_action->macro_id = sqlite3_column_int(stmt, i); - break; - case 'r': // relay_id - new_macro_action->relay_id = sqlite3_column_int(stmt, i); - break; - case 's': // schedule_id - new_macro_action->schedule_id = sqlite3_column_int(stmt, i); - break; - case 'w': // weekday - new_macro_action->weekday = (uint8_t)sqlite3_column_int(stmt, i); - break; - default: // ignore columns not implemented - break; - } - } - return new_macro_action; -} - -static macro_action_t** -macro_action_db_select(sqlite3_stmt *stmt) -{ - macro_action_t **all_macro_actions = malloc(sizeof(macro_action_t*)); - - int row = 0; - - for(;;) - { - int s; - - s = sqlite3_step(stmt); - if (s == SQLITE_ROW) - { - macro_action_t *new_macro_action = macro_action_db_select_mapper(stmt); - row++; - - all_macro_actions = (macro_action_t**)realloc(all_macro_actions, sizeof(macro_action_t*) * (row + 1)); - all_macro_actions[row - 1] = new_macro_action; - } - else - { - if(s == SQLITE_DONE) - { - break; - } - - LOGGER_ERR("error selecting macro_actions from database: %s\n", sqlite3_errstr(s)); - break; - } - } - sqlite3_finalize(stmt); - all_macro_actions[row] = NULL; - return all_macro_actions; -} - -int -macro_action_insert(macro_action_t *macro_action) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "INSERT INTO macro_actions(macro_id, relay_id, schedule_id, weekday) values (?1, ?2, ?3, ?4);", -1, &stmt, NULL); - - sqlite3_bind_int(stmt, 1, macro_action->macro_id); - sqlite3_bind_int(stmt, 2, macro_action->relay_id); - sqlite3_bind_int(stmt, 3, macro_action->schedule_id); - sqlite3_bind_int(stmt, 4, macro_action->weekday); - - int rc = sqlite3_step(stmt); - - sqlite3_finalize(stmt); - - cache_invalidate_macro(macro_action->macro_id); - - return rc != SQLITE_DONE; -} - -int -macro_action_delete_for_macro(int macro_id) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "DELETE FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, macro_id); - - int rc = sqlite3_step(stmt); - - sqlite3_finalize(stmt); - - cache_invalidate_macro(macro_id); - - return rc != SQLITE_DONE; -} - -macro_action_t** -macro_action_get_for_macro(int macro_id) -{ - LOGGER_DEBUG("getting macro_actions [macro_id=%d] from database\n", macro_id); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=?", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, macro_id); - - return macro_action_db_select(stmt); -} - -macro_action_t** -macro_action_get_for_macro_and_weekday(int macro_id, int weekday) -{ - LOGGER_DEBUG("getting macro_actions [macro_id=%d weekday=%d] from database\n", macro_id, weekday); - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=? AND weekday=?", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, macro_id); - sqlite3_bind_int(stmt, 2, weekday); - - return macro_action_db_select(stmt); -} - -int -macro_action_execute(macro_action_t *macro_action) -{ - schedule_t *schedule = schedule_get_by_id(macro_action->schedule_id); - if(!schedule) - { - return 1; - } - - relay_t *relay = relay_get_by_id(macro_action->relay_id); - if(!relay) - { - free(schedule); - return 1; - } - - schedule_free(relay->schedules[macro_action->weekday]); - relay->schedules[macro_action->weekday] = schedule; - - return relay_save(relay); -} - -void -macro_action_free_list(macro_action_t **macro_actions) -{ - for(int i = 0; macro_actions[i] != NULL; ++i) - { - free(macro_actions[i]); - } - free(macro_actions); -} - -int* -macro_action_get_macro_ids_with_schedule(int schedule_id) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE schedule_id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, schedule_id); - - return database_helper_get_ids(stmt); -} - -int* -macro_action_get_macro_ids_with_relay(int relay_id) -{ - sqlite3_stmt *stmt; - - sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE relay_id=?1;", -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, relay_id); - - return database_helper_get_ids(stmt); -} diff --git a/src/models/period.c b/src/models/period.c index c582164..a5a006a 100644 --- a/src/models/period.c +++ b/src/models/period.c @@ -27,8 +27,9 @@ period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm) } } - uint16_t tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10); - uint16_t tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10); + uint16_t tmp_h, tmp_m; + tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10); + tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10); if(tmp_h > 24 || tmp_m >= 60) { diff --git a/src/models/relay.c b/src/models/relay.c index 1707d22..df196f0 100644 --- a/src/models/relay.c +++ b/src/models/relay.c @@ -1,19 +1,18 @@ -#include <sqlite3.h> #include <stdlib.h> #include <string.h> +#include <sqlite3.h> -#include <cJSON.h> #include <cache.h> -#include <constants.h> -#include <database.h> +#include <cJSON.h> #include <logger.h> -#include <models/controller.h> -#include <models/junction_relay_schedule.h> -#include <models/junction_tag.h> -#include <models/relay.h> -#include <models/schedule.h> -#include <models/tag.h> +#include <database.h> #include <status.h> +#include <models/relay.h> +#include <models/controller.h> +#include <models/schedule.h> +#include <models/junction_tag.h> +#include <models/junction_relay_schedule.h> +#include <models/tag.h> static int db_update_insert(relay_t *relay, sqlite3_stmt *stmt) @@ -36,7 +35,6 @@ static relay_t* relay_db_select_mapper(sqlite3_stmt *stmt) { relay_t *new_relay = malloc(sizeof(relay_t)); - new_relay->id = 0; new_relay->is_on = 0; for(int i = 0; i < sqlite3_column_count(stmt); i++) @@ -54,8 +52,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt) switch(name[1]) { case 'a': // name - strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH); - new_relay->name[MAX_NAME_LENGTH] = '\0'; + strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127); break; case 'u': // number new_relay->number = sqlite3_column_int(stmt, i); @@ -69,7 +66,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt) } } - memset(new_relay->schedules, 0, sizeof(schedule_t*) * DAYS_PER_WEEK); + memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7); relay_reload_schedules(new_relay); relay_reload_active_schedule(new_relay); @@ -103,9 +100,11 @@ relay_db_select(sqlite3_stmt *stmt) { break; } - - LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s)); - break; + else + { + LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s)); + break; + } } } sqlite3_finalize(stmt); @@ -116,8 +115,7 @@ relay_db_select(sqlite3_stmt *stmt) int relay_save(relay_t *relay) { - database_transaction_lock lock; - database_transaction_begin(&lock); + int opened_transaction = database_transaction_begin(); sqlite3_stmt *stmt; if(relay->id) @@ -142,7 +140,10 @@ relay_save(relay_t *relay) LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database)); } - database_transaction_rollback(&lock); + if(opened_transaction) + { + database_transaction_rollback(); + } } else { @@ -155,14 +156,17 @@ relay_save(relay_t *relay) junction_relay_schedule_remove_for_relay(relay->id); LOGGER_DEBUG("rebuilding relay_schedule junction\n"); - int schedule_ids[DAYS_PER_WEEK]; - for(int i = 0; i < DAYS_PER_WEEK; ++i) + int schedule_ids[7]; + for(int i = 0; i < 7; ++i) { schedule_ids[i] = relay->schedules[i]->id; } junction_relay_schedule_insert_weekdays(relay->id, schedule_ids); - database_transaction_commit(&lock); + if(opened_transaction) + { + database_transaction_commit(); + } } cache_invalidate_relay(relay->id, -1); @@ -184,16 +188,17 @@ relay_reload_schedules(relay_t *relay) { schedule_t **schedules = schedule_get_relay_weekdays(relay->id); - uuid_t off_uid; - schedule_get_uid_off(off_uid); + uuid_t off_id; + memset(off_id, 0, sizeof(uuid_t)); + memcpy(off_id, "off", 3); int fill_with_off = 0; - for(int i = 0; i < DAYS_PER_WEEK; ++i) + for(int i = 0; i < 7; ++i) { if(schedules[i] == NULL || fill_with_off) { LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id); - relay->schedules[i] = schedule_get_by_uid(off_uid); + relay->schedules[i] = schedule_get_by_uid(off_id); fill_with_off = 1; } @@ -213,7 +218,7 @@ relay_reload_schedules(relay_t *relay) void relay_free(relay_t *relay) { - for(int i = 0; i < DAYS_PER_WEEK; ++i) + for(int i = 0; i < 7; ++i) { schedule_free(relay->schedules[i]); } @@ -306,7 +311,7 @@ relay_to_json(relay_t *relay, int status_relay) cJSON_AddItemToObject(json, "active_schedule", json_active_schedule); cJSON *json_schedules = cJSON_CreateArray(); - for(int i = 0; i < DAYS_PER_WEEK; ++i) + for(int i = 0; i < 7; ++i) { cJSON *json_schedule = schedule_to_json(relay->schedules[i]); cJSON_AddItemToArray(json_schedules, json_schedule); diff --git a/src/models/schedule.c b/src/models/schedule.c index d425b9b..abb6850 100644 --- a/src/models/schedule.c +++ b/src/models/schedule.c @@ -37,8 +37,6 @@ schedule_db_select_mapper(sqlite3_stmt *stmt) { const uint16_t *periods_blob; schedule_t *new_schedule = malloc(sizeof(schedule_t)); - new_schedule->id = 0; - for(int i = 0; i < sqlite3_column_count(stmt); i++) { const char *name = sqlite3_column_name(stmt, i); @@ -48,8 +46,8 @@ schedule_db_select_mapper(sqlite3_stmt *stmt) new_schedule->id = sqlite3_column_int(stmt, i); break; case 'n': // name - strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH); - new_schedule->name[MAX_NAME_LENGTH] = '\0'; + strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), 127); + new_schedule->name[127] = '\0'; break; case 'p': // periods periods_blob = sqlite3_column_blob(stmt, i); @@ -99,9 +97,11 @@ schedule_db_select(sqlite3_stmt *stmt) { break; } - - LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s)); - break; + else + { + LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s)); + break; + } } } sqlite3_finalize(stmt); @@ -112,8 +112,7 @@ schedule_db_select(sqlite3_stmt *stmt) int schedule_save(schedule_t *schedule) { - database_transaction_lock lock; - database_transaction_begin(&lock); + int opened_transaction = database_transaction_begin(); sqlite3_stmt *stmt; if(schedule->id) @@ -138,7 +137,10 @@ schedule_save(schedule_t *schedule) LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database)); } - database_transaction_rollback(&lock); + if(opened_transaction) + { + database_transaction_rollback(); + } } else { @@ -147,7 +149,10 @@ schedule_save(schedule_t *schedule) schedule->id = sqlite3_last_insert_rowid(global_database); } - database_transaction_commit(&lock); + if(opened_transaction) + { + database_transaction_commit(); + } } cache_invalidate_schedule(schedule->id); @@ -177,16 +182,18 @@ schedule_remove(schedule_t *schedule) int schedule_is_protected(schedule_t *schedule) { - uuid_t tmp_uid; + uuid_t tmp_uuid; - schedule_get_uid_off(tmp_uid); - if(uuid_compare(schedule->uid, tmp_uid) == 0) + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); + if(uuid_compare(schedule->uid, tmp_uuid) == 0) { return 1; } - schedule_get_uid_on(tmp_uid); - if(uuid_compare(schedule->uid, tmp_uid) == 0) + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "on", 2); + if(uuid_compare(schedule->uid, tmp_uuid) == 0) { return 1; } @@ -278,10 +285,9 @@ schedule_to_json(schedule_t *schedule) continue; } - char start_str[8]; - char end_str[8]; + char start_str[8], end_str[8]; period_t *period = &schedule->periods[i]; - sprintf(start_str, "%02d:%02d", period->start / 60 , period->start % 60); + sprintf(start_str, "%02d:%02d", period->start / 60, period->start % 60); sprintf(end_str, "%02d:%02d", period->end / 60, period->end % 60); cJSON *json_period_start = cJSON_CreateString(start_str); @@ -358,10 +364,11 @@ schedule_get_by_id_or_off(int id) return result; } - uuid_t tmp_uid; - schedule_get_uid_off(tmp_uid); + uuid_t tmp_uuid; + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); - return schedule_get_by_uid(tmp_uid); + return schedule_get_by_uid(tmp_uuid); } schedule_t* @@ -402,10 +409,11 @@ schedule_get_by_uid_or_off(uuid_t uid) return result; } - uuid_t tmp_uid; - schedule_get_uid_off(tmp_uid); + uuid_t tmp_uuid; + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); - return schedule_get_by_uid(tmp_uid); + return schedule_get_by_uid(tmp_uuid); } schedule_t* @@ -456,12 +464,14 @@ schedule_uid_parse(const char *uid_str, uuid_t result) { if(strcmp("off", uid_str) == 0) { - schedule_get_uid_off(result); + memset(result, 0, sizeof(uuid_t)); + memcpy(result, "off", 3); return 0; } if(strcmp("on", uid_str) == 0) { - schedule_get_uid_on(result); + memset(result, 0, sizeof(uuid_t)); + memcpy(result, "on", 2); return 0; } @@ -475,37 +485,23 @@ schedule_uid_parse(const char *uid_str, uuid_t result) void schedule_uid_unparse(const uuid_t uid, char *result) { - uuid_t tmp_uid; + uuid_t tmp_uuid; - schedule_get_uid_off(tmp_uid); - if(uuid_compare(uid, tmp_uid) == 0) + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); + if(uuid_compare(uid, tmp_uuid) == 0) { - strncpy(result, "off", 4); - result[3] = '\0'; + strcpy(result, "off"); return; } - schedule_get_uid_on(tmp_uid); - if(uuid_compare(uid, tmp_uid) == 0) + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "on", 2); + if(uuid_compare(uid, tmp_uuid) == 0) { - strncpy(result, "on", 3); - result[2] = '\0'; + strcpy(result, "on"); return; } uuid_unparse(uid, result); } - -void -schedule_get_uid_off(uuid_t target) -{ - uuid_clear(target); - strncpy((char*)target, "off", 4); -} - -void -schedule_get_uid_on(uuid_t target) -{ - uuid_clear(target); - strncpy((char*)target, "on", 3); -} diff --git a/src/router.c b/src/router.c index dfc68db..6c74428 100644 --- a/src/router.c +++ b/src/router.c @@ -10,7 +10,6 @@ #include <endpoints/api_v1_controllers.h> #include <endpoints/api_v1_relays.h> #include <endpoints/api_v1_tags.h> -#include <endpoints/api_v1_macros.h> #include <endpoints/api_v1_ws.h> static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT]; @@ -72,7 +71,7 @@ router_init() router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_DELETE, api_v1_schedules_STR_DELETE); router_register_endpoint("/api/v1/schedules/tag/{str}", HTTP_METHOD_GET, api_v1_schedules_tag_STR_GET); - router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_PUT, api_v1_controllers_discover_PUT); + router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_POST, api_v1_controllers_discover_POST); router_register_endpoint("/api/v1/controllers/", HTTP_METHOD_GET, api_v1_controllers_GET); router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_GET, api_v1_controllers_STR_GET); router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_PUT, api_v1_controllers_STR_PUT); @@ -91,13 +90,6 @@ router_init() router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_GET, api_v1_tags_STR_GET); router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_DELETE, api_v1_tags_STR_DELETE); - router_register_endpoint("/api/v1/macros/", HTTP_METHOD_GET, api_v1_macros_GET); - router_register_endpoint("/api/v1/macros/", HTTP_METHOD_POST, api_v1_macros_POST); - router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_GET, api_v1_macros_STR_GET); - router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_PUT, api_v1_macros_STR_PUT); - router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_DELETE, api_v1_macros_STR_DELETE); - router_register_endpoint("/api/v1/macros/{str}/execute", HTTP_METHOD_PUT, api_v1_macros_STR_execute_PUT); - router_register_endpoint("/api/v1/ws/relays", HTTP_METHOD_WEBSOCKET, api_v1_ws_relays); } @@ -154,7 +146,7 @@ router_register_endpoint(const char *route, int method, endpoint_func_f func) // +1 for NULL terminator endpoint->route = malloc(sizeof(char*) * (route_parts_count + 1)); endpoint->route_keeper = malloc(sizeof(char) * (route_len + 1)); - strncpy(endpoint->route_keeper, route, route_len + 1); + strncpy(endpoint->route_keeper, route, route_len); endpoint->route_keeper[route_len] = '\0'; int route_part = 0; @@ -313,6 +305,7 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_ continue; } endpoints[i].possible_route = 0; + continue; } uri_token = strtok_r(NULL, delimiter, &uri_token_save); ++route_part; @@ -340,10 +333,8 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_ { if(best_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR) { - size_t arg_value_str_len = strlen(best_endpoint->args[i].value.v_str); - char *arg_value_str = malloc(sizeof(char) * (arg_value_str_len + 1)); + char *arg_value_str = malloc(sizeof(char) * (strlen(best_endpoint->args[i].value.v_str) + 1)); strcpy(arg_value_str, best_endpoint->args[i].value.v_str); - arg_value_str[arg_value_str_len] = '\0'; best_endpoint->args[i].value.v_str = arg_value_str; } } diff --git a/tests/controller.testing.ini b/tests/controller.testing.ini new file mode 100644 index 0000000..d3577f6 --- /dev/null +++ b/tests/controller.testing.ini @@ -0,0 +1,65 @@ +[controller] +name = new emgauwa device + +: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env +discovery-port = 4422 +: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env +mqtt-port = 1886 +mqtt-host = localhost + +relay-count = 10 +database = controller.sqlite +log-level = debug +log-file = stdout + +[relay-0] +driver = piface +pin = 0 +inverted = 0 + +[relay-1] +driver = piface +pin = 1 +inverted = 0 + +[relay-2] +driver = gpio +pin = 5 +inverted = 1 + +[relay-3] +driver = gpio +pin = 4 +inverted = 1 + +[relay-4] +driver = gpio +pin = 3 +inverted = 1 + +[relay-5] +driver = gpio +pin = 2 +inverted = 1 + +[relay-6] +driver = gpio +pin = 1 +inverted = 1 +pulse-duration = 3 + +[relay-7] +driver = gpio +pin = 0 +inverted = 1 +pulse-duration = 3 + +[relay-8] +driver = gpio +pin = 16 +inverted = 1 + +[relay-9] +driver = gpio +pin = 15 +inverted = 1 diff --git a/tests/core.testing.ini b/tests/core.testing.ini new file mode 100644 index 0000000..c3710a1 --- /dev/null +++ b/tests/core.testing.ini @@ -0,0 +1,16 @@ +[core] +server-port = 5000 +database = core.sqlite +content-dir = /usr/share/webapps/emgauwa +not-found-file = 404.html +not-found-file-mime = text/html +not-found-content = 404 - NOT FOUND +not-found-content-type = text/plain + +: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env +discovery-port = 4422 +: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env +mqtt-port = 1886 + +log-level = debug +log-file = stdout diff --git a/tests/emgauwa-controller-testing.conf b/tests/emgauwa-controller-testing.conf deleted file mode 100644 index 5b541e4..0000000 --- a/tests/emgauwa-controller-testing.conf +++ /dev/null @@ -1,66 +0,0 @@ -[controller] -database = "emgauwa-controller.sqlite" - -mqtt-host = "127.0.0.1" - -[ports] -# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env -discovery = 4422 -# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env -mqtt = 1886 - -[logging] -level = "debug" -file = "stdout" - -[[relays]] -driver = "piface" -pin = 0 -inverted = 0 - -[[relays]] -driver = "piface" -pin = 1 -inverted = 0 - -[[relays]] -driver = "gpio" -pin = 5 -inverted = 1 - -[[relays]] -driver = "gpio" -pin = 4 -inverted = 1 - -[[relays]] -driver = "gpio" -pin = 3 -inverted = 1 - -[[relays]] -driver = "gpio" -pin = 2 -inverted = 1 - -[[relays]] -driver = "gpio" -pin = 1 -inverted = 1 -pulse-duration = 3 - -[[relays]] -driver = "gpio" -pin = 0 -inverted = 1 -pulse-duration = 3 - -[[relays]] -driver = "gpio" -pin = 16 -inverted = 1 - -[[relays]] -driver = "gpio" -pin = 15 -inverted = 1 diff --git a/tests/emgauwa-core-testing.conf b/tests/emgauwa-core-testing.conf deleted file mode 100644 index c3896b2..0000000 --- a/tests/emgauwa-core-testing.conf +++ /dev/null @@ -1,17 +0,0 @@ -[core] -database = "emgauwa-core.sqlite" -include = "../emgauwa-core-testing.conf.d" -content-dir = "." -not-found-file = "404.html" -not-found-file-mime = "text/html" - -[ports] -server = 5000 -# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env -discovery = 4422 -# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env -mqtt = 1886 - -[logging] -level = "debug" -file = "stdout" diff --git a/tests/emgauwa-core-testing.conf.d/conf_d_working.conf b/tests/emgauwa-core-testing.conf.d/conf_d_working.conf deleted file mode 100644 index ac1dfee..0000000 --- a/tests/emgauwa-core-testing.conf.d/conf_d_working.conf +++ /dev/null @@ -1,3 +0,0 @@ -[core] -not-found-content = '{"msg": "conf.d working"}' -not-found-content-type = "application/json" diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 15b1d18..cbfbdb1 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,9 +1,14 @@ #!/usr/bin/env sh source_dir=$PWD -working_dir=$PWD/testing +working_dir=$PWD/testing_latest +working_bak=$PWD/testing_bak -alias valgrind_emgauwa="valgrind -s $2 --log-file=$working_dir/valgrind.log" +alias valgrind_emgauwa="valgrind $2 --log-file=$working_dir/valgrind.log" + + +rm -rf $working_bak +[ -d $working_dir ] && mv $working_dir $working_bak mkdir -p $working_dir cd $working_dir @@ -12,18 +17,15 @@ target_branch=$(git rev-parse --abbrev-ref HEAD) if [ -z "$EMGAUWA_CONTROLLER_EXE" ] then - mkdir -p controller - cd controller - - echo "Trying to pull or clone controller" - git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git . >/dev/null 2>&1 || git pull >/dev/null || exit + git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git controller || exit + cd ./controller git checkout dev >/dev/null 2>&1 git checkout $target_branch >/dev/null 2>&1 git checkout $2 >/dev/null 2>&1 echo "Building controller on branch $(git rev-parse --abbrev-ref HEAD)" - mkdir -p build + mkdir build cd build cmake -DWIRING_PI_DEBUG=on .. >/dev/null @@ -33,16 +35,20 @@ fi echo "Emgauwa controller: $($EMGAUWA_CONTROLLER_EXE --version)" -$EMGAUWA_CONTROLLER_EXE -c $source_dir/emgauwa-controller-testing.conf >$working_dir/controller.log 2>&1 & +$EMGAUWA_CONTROLLER_EXE start -c $source_dir/controller.testing.ini >$working_dir/controller.log 2>&1 & controller_id=$! cd $working_dir -touch $working_dir/index.html - cp $1 $working_dir/core +cp $source_dir/core.testing.ini $working_dir/core.ini -valgrind_emgauwa $working_dir/core -c $source_dir/emgauwa-core-testing.conf >>$working_dir/core.log 2>&1 & +echo "=== invalids start (must exit) ===" >$working_dir/core.log +$working_dir/core >>$working_dir/core.log 2>&1 +$working_dir/core INVALID_ACTION >>$working_dir/core.log 2>&1 + +echo "=== valid start ===" >>$working_dir/core.log +valgrind_emgauwa $working_dir/core start >>$working_dir/core.log 2>&1 & core_id=$! @@ -62,12 +68,4 @@ test_result=$? kill $core_id kill $controller_id -timestamp=$(date -Iseconds) -for backup_file in core.log controller.log valgrind.log emgauwa-core.sqlite; do - mv $backup_file $timestamp.$backup_file - ln -sf $timestamp.$backup_file latest.$backup_file -done - -cat latest.valgrind.log - exit $test_result diff --git a/tests/tavern_tests/0.1.test_basics.tavern.yaml b/tests/tavern_tests/0.1.test_basics.tavern.yaml deleted file mode 100644 index 8eff061..0000000 --- a/tests/tavern_tests/0.1.test_basics.tavern.yaml +++ /dev/null @@ -1,25 +0,0 @@ -test_name: "[test_basics] Test basic calls" - -stages: -- name: "[test_basics] get index" - request: - url: "http://localhost:5000/" - method: GET - response: - status_code: 200 - -- name: "[test_basics] get 404" - request: - url: "http://localhost:5000/invalid_url_for_testing_do_not_use" - method: GET - response: - status_code: 404 - -- name: "[test_basics] verify conf.d by 404" - request: - url: "http://localhost:5000/invalid_url_for_testing_do_not_use" - method: GET - response: - status_code: 404 - json: - msg: "conf.d working" diff --git a/tests/tavern_tests/1.0.controllers_basic.tavern.yaml b/tests/tavern_tests/1.0.controllers_basic.tavern.yaml index 3bce298..a48ea17 100644 --- a/tests/tavern_tests/1.0.controllers_basic.tavern.yaml +++ b/tests/tavern_tests/1.0.controllers_basic.tavern.yaml @@ -3,7 +3,7 @@ test_name: Test basic controller functions stages: - name: "[controllers_basic] discover controllers" request: - method: PUT + method: POST url: "http://localhost:5000/api/v1/controllers/discover/" response: status_code: 200 @@ -86,7 +86,7 @@ stages: - name: "[controllers_basic] discover controllers again" request: - method: PUT + method: POST url: "http://localhost:5000/api/v1/controllers/discover/" response: status_code: 200 diff --git a/tests/tavern_tests/1.1.controller_relays_basic.tavern.yaml b/tests/tavern_tests/1.1.controller_relays_basic.tavern.yaml index 285d2d5..f0d1750 100644 --- a/tests/tavern_tests/1.1.controller_relays_basic.tavern.yaml +++ b/tests/tavern_tests/1.1.controller_relays_basic.tavern.yaml @@ -26,7 +26,7 @@ stages: extra_kwargs: relay_count: !int "{returned_relay_count:d}" -- name: "[controller_relays_basic] get controller relay" +- name: "[controller_relays_basic] get controller relays, check length" request: method: GET url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/5" @@ -40,69 +40,3 @@ stages: function: validate_relay:check_number extra_kwargs: number: 5 - -- name: "[controller_relays_basic] get controller relay with invalid uid" - request: - method: GET - url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/5" - response: - status_code: 400 - -- name: "[controller_relays_basic] get controller relay with unavailable uid" - request: - method: GET - url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/5" - response: - status_code: 404 - -- name: "[controller_relays_basic] get controller relay with invalid number" - request: - method: GET - url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number" - response: - status_code: 404 - -- name: "[controller_relays_basic] get controller relay with unavailable number" - request: - method: GET - url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001" - response: - status_code: 404 - - - - -- name: "[controller_relays_basic] pulse relay" - request: - method: POST - url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/6/pulse" - response: - status_code: 200 - -- name: "[controller_relays_basic] pulse relay with invalid uid" - request: - method: POST - url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/6/pulse" - response: - status_code: 400 - -- name: "[controller_relays_basic] pulse relay with unavailable uid" - request: - method: POST - url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/6/pulse" - response: - status_code: 404 - -- name: "[controller_relays_basic] pulse relay with invalid number" - request: - method: POST - url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number/pulse" - response: - status_code: 404 - -- name: "[controller_relays_basic] pulse relay with unavailable number" - request: - method: POST - url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001/pulse" - response: - status_code: 404 diff --git a/tests/tavern_tests/3.0.tags.tavern.yaml b/tests/tavern_tests/3.0.tags.tavern.yaml index 9004442..d4ef67f 100644 --- a/tests/tavern_tests/3.0.tags.tavern.yaml +++ b/tests/tavern_tests/3.0.tags.tavern.yaml @@ -98,13 +98,6 @@ stages: controller_id: "{returned_id}" tag: "{returned_tag}" -- name: "[tags] get returned tag with relays and schedules" - request: - method: GET - url: "http://localhost:5000/api/v1/tags/{returned_tag}" - response: - status_code: 200 - - name: "[tags] get tags" request: method: GET @@ -113,56 +106,3 @@ stages: status_code: 200 verify_response_with: function: validate_tag:multiple - -- name: "[tags] get unavailable tag" - request: - method: GET - url: "http://localhost:5000/api/v1/tags/invalid_unavailable_tag" - response: - status_code: 404 - -- name: "[tags] post tag" - request: - method: POST - url: "http://localhost:5000/api/v1/tags/" - json: - tag: "unused_tag_1" - response: - status_code: 201 - -- name: "[tags] get posted tag" - request: - method: GET - url: "http://localhost:5000/api/v1/tags/unused_tag_1" - response: - status_code: 200 - -- name: "[tags] delete posted tag" - request: - method: DELETE - url: "http://localhost:5000/api/v1/tags/unused_tag_1" - response: - status_code: 200 - -- name: "[tags] get deleted tag" - request: - method: GET - url: "http://localhost:5000/api/v1/tags/unused_tag_1" - response: - status_code: 404 - -- name: "[tags] delete deleted tag again" - request: - method: DELETE - url: "http://localhost:5000/api/v1/tags/unused_tag_1" - response: - status_code: 404 - -- name: "[tags] get tags again" - request: - method: GET - url: "http://localhost:5000/api/v1/tags/" - response: - status_code: 200 - verify_response_with: - function: validate_tag:multiple diff --git a/tests/tavern_utils/validate_tag.py b/tests/tavern_utils/validate_tag.py index 6a3004a..1e907e5 100644 --- a/tests/tavern_utils/validate_tag.py +++ b/tests/tavern_utils/validate_tag.py @@ -11,9 +11,24 @@ def multiple(response): for tag in response.json(): _verify_single(tag) -def find(response, tag): - print(response.json()) - for response_tag in response.json(): - if response_tag == tag: - return - assert False, "tag not found in list" +#def find(response, name=None, number=None, controller_id=None, tag=None): +# print(response.json()) +# for tag in response.json(): +# if number != None and number != tag.get("number"): +# continue +# +# if name != None and name != tag.get("name"): +# continue +# +# if controller_id != None and controller_id != tag.get("controller_id"): +# continue +# +# if tag != None: +# found_in_response = False +# for response_tag in tag.get("tags"): +# if response_tag == tag: +# found_in_response = True +# if not found_in_response: +# continue +# return +# assert False, "tag not found in list" diff --git a/vendor/confini.c b/vendor/confini.c new file mode 100644 index 0000000..25353a7 --- /dev/null +++ b/vendor/confini.c @@ -0,0 +1,5016 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* Please make sure that the TAB width in your editor is set to 4 spaces */ + +/** + + @file confini.c + @brief libconfini functions + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.14.0 + @date 2016-2020 + @see https://madmurphy.github.io/libconfini + +**/ + + + /*/| + (_|_) _ _ _ __ _ _ + | (_) |__ ___ ___ _ __ / _(_)_ __ (_) + | | | '_ \ / __/ _ \| '_ \| |_| | '_ \| | + | | | |_) | (_| (_) | | | | _| | | | | | + |_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _ + ( | ) + |/*/ + + + +/** + + + @def INIFORMAT_TABLE_AS(_____) + + Content of the table: + + - Bits 1-19: INI syntax + - Bits 20-22: INI semantics + - Bits 23-24: Human syntax (disabled entries) + + + + @typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data + ) + + @param statistics + A pointer to the #IniStatistics to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniDispHandler) ( + IniDispatch *dispatch, + void * user_data + ) + + @param dispatch + A pointer to the #IniDispatch to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string to handle + @param string_length + The length of the INI string in bytes + @param string_num + The unique number that identifies @p ini_string within a + sequence of INI strings; it equals zero if @p ini_string is the + first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been extracted + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string containing the fragment to handle + @param fragm_offset + The offset of the selected fragment in bytes + @param fragm_length + The length of the selected fragment in bytes + @param fragm_num + The unique number that identifies the selected fragment within a + sequence of fragments of @p ini_string; it equals zero if the + fragment is the first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been + extracted + @param user_data + The custom argument previously passed to the caller function + + + + @struct IniFormat + + @property IniFormat::delimiter_symbol + The key-value delimiter character (ASCII only allowed); if set + to `\0`, any space is delimiter + (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format + given, `IniFormat::delimiter_symbol` matches a metacharacter + (`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have + higher priority than its role as delimiter symbol (i.e., the + format will have no key-value delimiter); you may use the + #IniDelimiters `enum` for this. + @property IniFormat::case_sensitive + If set to `true`, string comparisons will be always + case-sensitive. + @property IniFormat::semicolon_marker + The rule of the semicolon character (use `enum` + #IniCommentMarker for this). + @property IniFormat::hash_marker + The rule of the hash character (use `enum` #IniCommentMarker for + this). + @property IniFormat::section_paths + Defines whether and how the format supports sections (use `enum` + #IniSectionPaths for this). + @property IniFormat::multiline_nodes + Defines which class of entries are allowed to be multi-line (use + `enum` #IniMultiline for this). + @property IniFormat::no_spaces_in_names + If set to `true`, key and section names containing spaces (even + within quotes) will be rendered as #INI_UNKNOWN. Note that + setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not + automatically set this option to `true` (spaces will still be + allowed in section names). + @property IniFormat::no_single_quotes + If set to `true`, the single-quote character (`'`) will be + considered as a normal character. + @property IniFormat::no_double_quotes + If set to `true`, the double-quote character (`"`) will be + considered as a normal character. + @property IniFormat::implicit_is_not_empty + If set to `true`, implicit keys (see @ref libconfini) will + be always dispatched using the values given by the global + variables #INI_GLOBAL_IMPLICIT_VALUE and + #INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value + and to #IniDispatch::v_len respectively; if set to `false`, + implicit keys will be considered to be empty keys. + @property IniFormat::do_not_collapse_values + If set to `true`, sequences of one or more spaces in values + (`/\s+/`) will be dispatched verbatim. + @property IniFormat::preserve_empty_quotes + If set to `true`, and if single/double quotes are + metacharacters, ensures that, within values, empty strings + enclosed between quotes (`""` or `''`) will not be collapsed + together with the spaces that surround them. This option is + useful for values containing space-delimited arrays, in order to + preserve their empty members -- as in, for instance: + `coordinates = "" ""`. Note that, in section and key names, + empty strings enclosed between quotes are _always_ collapsed + together with their surrounding spaces. + @property IniFormat::disabled_after_space + If set to `true`, what follows `/[#;]\s/` is allowed to be + parsed as a disabled entry. + @property IniFormat::disabled_can_be_implicit + If set to `false`, comments that do not contain a key-value + delimiter will never be parsed as disabled keys, but always as + simple comments (even if the format supports implicit keys). + + + + @struct IniStatistics + + @property IniStatistics::format + The format of the INI file (see #IniFormat) + @property IniStatistics::bytes + The size in bytes of the parsed file + @property IniStatistics::members + The size in number of members (nodes) of the parsed file -- this + number always equals the number of dispatches that will be sent + by #load_ini_file(), #load_ini_path() or #strip_ini_cache() + + + + @struct IniDispatch + + @property IniDispatch::format + The format of the INI file (see #IniFormat) + @property IniDispatch::type + The dispatch type (see `enum` #IniNodeType) + @property IniDispatch::data + #IniDispatch::data can contain a comment, a section path or a + key name depending, on #IniDispatch::type; cannot be `NULL` + @property IniDispatch::value + It can be the value of a key element, an empty string or it can + point to the address pointed by the global variable + #INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in + which `IniDispatch::value` can be `NULL`_) + @property IniDispatch::append_to + The current section path; cannot be `NULL` + @property IniDispatch::d_len + The length of the string #IniDispatch::data + @property IniDispatch::v_len + The length of the string #IniDispatch::value + @property IniDispatch::at_len + The length of the string #IniDispatch::append_to + @property IniDispatch::dispatch_id + The dispatch number (the first dispatch is number zero) + + +**/ + + + + /*\ + |*| + |*| LOCAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* PREPROCESSOR PREAMBLE */ + + +/* String concatenation facilities */ +#define __PP_CAT__(STR1, STR2) STR1##STR2 +#define __PP_UCAT__(STR1, STR2) STR1##_##STR2 +#define __PP_EVALUCAT__(STR1, STR2) __PP_UCAT__(STR1, STR2) + + + + /* PREPROCESSOR ENVIRONMENT */ + + +#ifndef CONFINI_IO_FLAVOR +/** + + @brief The I/O API to use (possibly overridden via + `-DCONFINI_IO_FLAVOR=[FLAVOR]`) + + Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX` + +**/ +#define CONFINI_IO_FLAVOR CONFINI_STANDARD +#endif + + + + /* PREPROCESSOR GLUE */ + + +#define _LIBCONFINI_PRIVATE_ITEM_(GROUP, ITEM) \ + __PP_EVALUCAT__(__PP_CAT__(_LIB, GROUP), __PP_CAT__(ITEM, _)) +#define _LIBCONFINI_CURRENT_FLAVOR_GET_(NAME) \ + _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME) +#define _LIBCONFINI_IS_FLAVOR_(NAME) \ + _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \ + _LIBCONFINI_PRIVATE_ITEM_(NAME, FLAVOR) + + + + /* AVAILABLE I/O FLAVORS */ + + +/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default, REQUIRED) */ +#define _LIBCONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END) +#define _LIBCONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR) +#define _LIBCONFINI_STANDARD_FT_T_ long signed int +/* Any unique non-zero integer to identify this I/O API */ +#define _LIBCONFINI_STANDARD_FLAVOR_ 1 + +/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */ +#define _LIBCONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END) +#define _LIBCONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR) +#define _LIBCONFINI_POSIX_FT_T_ off_t +/* Any unique non-zero integer to identify this I/O API */ +#define _LIBCONFINI_POSIX_FLAVOR_ 2 + +/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */ +#if _LIBCONFINI_IS_FLAVOR_(CONFINI_POSIX) +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +/* It is possible to add other I/O APIs here. Feel free to contribute! */ + + + + /* CHECKS */ + + +#if _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0 +#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR +#endif + + + + /* HEADERS */ + +#include <stdlib.h> +#include "confini.h" + + + + /* ALIASES */ + + +#define _LIBCONFINI_FALSE_ 0 +#define _LIBCONFINI_TRUE_ 1 +#define _LIBCONFINI_CHARBOOL_ unsigned char +#define _LIBCONFINI_SIMPLE_SPACE_ ' ' +#define _LIBCONFINI_HT_ '\t' +#define _LIBCONFINI_FF_ '\f' +#define _LIBCONFINI_VT_ '\v' +#define _LIBCONFINI_CR_ '\r' +#define _LIBCONFINI_LF_ '\n' +#define _LIBCONFINI_BACKSLASH_ '\\' +#define _LIBCONFINI_OPEN_SECTION_ '[' +#define _LIBCONFINI_CLOSE_SECTION_ ']' +#define _LIBCONFINI_SUBSECTION_ '.' +#define _LIBCONFINI_SEMICOLON_ ';' +#define _LIBCONFINI_HASH_ '#' +#define _LIBCONFINI_DOUBLE_QUOTES_ '"' +#define _LIBCONFINI_SINGLE_QUOTES_ '\'' +#define _LIBCONFINI_SEEK_EOF_(FILEPTR) \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR) +#define _LIBCONFINI_FTELL_(FILEPTR) \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR) +#define _LIBCONFINI_OFF_T_ \ + _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_T) + + + + /* FUNCTIONAL MACROS AND CONSTANTS */ + + +/* The character that will replace sequences of one or more spaces (`/\s+/`) */ +#define _LIBCONFINI_COLLAPSED_ _LIBCONFINI_SIMPLE_SPACE_ + + +/* + + These may be any character in theory... But after the left-trim of each node + leading spaces work pretty well as metacharacters... + +*/ +/* Internal marker of standard comments */ +#define _LIBCONFINI_SC_INT_MARKER_ _LIBCONFINI_SIMPLE_SPACE_ +/* Internal marker of inline comments */ +#define _LIBCONFINI_IC_INT_MARKER_ _LIBCONFINI_HT_ + + +/* + + Checks whether a character can be escaped within a given format + +*/ +#define _LIBCONFINI_IS_ESC_CHAR_(CHR, FMT) ( \ + CHR == _LIBCONFINI_BACKSLASH_ ? \ + !INIFORMAT_HAS_NO_ESC(FMT) \ + : CHR == _LIBCONFINI_DOUBLE_QUOTES_ ? \ + !FMT.no_double_quotes \ + : \ + CHR == _LIBCONFINI_SINGLE_QUOTES_ && !FMT.no_single_quotes \ + ) + + +/* + + Checks whether a character represents any marker within a given format + +*/ +#define _LIBCONFINI_IS_ANY_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER \ + ) + + +/* + + Checks whether a character represents any marker except `INI_IGNORE` within a + given format + +*/ +#define _LIBCONFINI_IS_COM_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER && FMT.hash_marker != INI_IGNORE \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER && FMT.semicolon_marker != INI_IGNORE \ + ) + + +/* + + Checks whether a character represents a marker of type `INI_DISABLED_OR_COMMENT` + within a given format + +*/ +#define _LIBCONFINI_IS_DIS_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker == INI_DISABLED_OR_COMMENT \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \ + ) + + +/* + + Checks whether a character represents a marker of type `INI_IGNORE` within a + given format + +*/ +#define _LIBCONFINI_IS_IGN_MARKER_(CHR, FMT) ( \ + CHR == _LIBCONFINI_HASH_ ? \ + FMT.hash_marker == INI_IGNORE \ + : \ + CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_IGNORE \ + ) + + +/* + + Checks whether a pointer is within the range + `INI_GLOBAL_IMPLICIT_VALUE >= PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN` + +*/ +#define _LIBCONFINI_IMPLICIT_RANGE_(PTR) \ + (PTR >= INI_GLOBAL_IMPLICIT_VALUE && PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN) + + +/* + + Maybe in the future there will be support for UTF-8 casefold (although probably + not -- see "Code considerations" in the Manual), but for now only ASCII... + +*/ +#define _LIBCONFINI_CHR_CASEFOLD_(CHR) (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR) + + +/* + + Possible depths of `_LIBCONFINI_SPACES_` (see function #is_some_space()). + + Please, consider the following three constants as belonging together to a + virtual opaque `enum`. + +*/ +#define _LIBCONFINI_WITH_EOL_ -1 +#define _LIBCONFINI_NO_EOL_ 1 +#define _LIBCONFINI_JUST_S_T_ 3 + + +/* + + Other constants related to `_LIBCONFINI_SPACES_` + +*/ +#define _LIBCONFINI_EOL_IDX_ 0 +#define _LIBCONFINI_SPALEN_ 6 + + +/* + + The list of space characters -- do not change its order or its content! + +*/ +static const char _LIBCONFINI_SPACES_[_LIBCONFINI_SPALEN_] = { + _LIBCONFINI_LF_, + _LIBCONFINI_CR_, + _LIBCONFINI_VT_, + _LIBCONFINI_FF_, + _LIBCONFINI_HT_, + _LIBCONFINI_SIMPLE_SPACE_ +}; + + +/** + + @brief A list of possible string representations of boolean pairs + + There may be infinite pairs here. Each pair must be organized according to the + following order: + + 1. Signifier of `false` + 2. Signifier of `true` + + @note Everything **must** be lowercase in this list. + +**/ +static const char * const INI_BOOLEANS[][2] = { + { "no", "yes" }, + { "false", "true" }, + { "off", "on" } +}; + + + + /* ABSTRACT UTILITIES */ + + +/** + + @brief Checks whether a character is a space + @param chr The target character + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return A boolean: `true` if the character matches, `false` otherwise + +**/ +static inline _LIBCONFINI_CHARBOOL_ is_some_space (const char chr, const int8_t depth) { + register int8_t idx = depth; + while (++idx < _LIBCONFINI_SPALEN_ && chr != _LIBCONFINI_SPACES_[idx]); + return idx < _LIBCONFINI_SPALEN_; +} + + +/** + + @brief Soft left trim -- does not change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_s (const char * const str, const size_t offs, const int8_t depth) { + register size_t idx = offs; + while (is_some_space(str[idx++], depth)); + return idx - 1; +} + + +/** + + @brief Hard left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_h (char * const str, const size_t offs, const int8_t depth) { + register size_t idx = offs; + while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; } + return idx; +} + + +/** + + @brief Shifting left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t ltrim_hh (char * const str, const size_t offs, const int8_t depth) { + register size_t idx_d = offs, idx_s = offs; + while (is_some_space(str[idx_s++], depth)); + if (--idx_s - idx_d) { + while ((str[idx_d++] = str[idx_s++])); + for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0'); + return idx_d - 1; + } + while (str[idx_s++]); + return idx_s - 1; +} + + +/** + + @brief Soft right trim -- does not change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The length of the string until the last non-space character + +**/ +static inline size_t rtrim_s (const char * const str, const size_t len, const int8_t depth) { + register size_t idx = len + 1; + while (--idx > 0 && is_some_space(str[idx - 1], depth)); + return idx; +} + + +/** + + @brief Hard right trim -- **does** change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_LIBCONFINI_WITH_EOL_`, + `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t rtrim_h (char * const str, const size_t len, const int8_t depth) { + register size_t idx = len; + while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; } + return idx; +} + + +/** + + @brief Unescaped soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`) + -- does not change the buffer + @param str The target string + @param len The length of the string + @return The length of the string until the last non-space character + +**/ +static inline size_t urtrim_s (const char * const str, const size_t len) { + + register uint8_t abcd = 1; + register size_t idx = len; + + + /* \ /\ + \ */ continue_urtrim: /* \ + \/ ______________________ \ */ + + + if (idx < 1) { + + return idx; + + } + + switch (str[--idx]) { + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = 1; + goto continue_urtrim; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = 3; + goto continue_urtrim; + + case _LIBCONFINI_BACKSLASH_: + + if (abcd >>= 1) { + + goto continue_urtrim; + + } + + } + + return idx + 1; + +} + + +/** + + @brief Converts an ASCII string to lower case + @param str The target string + @return Nothing + +**/ +static inline void string_tolower (char * const str) { + for (register char * chrptr = str; *chrptr; chrptr++) { + *chrptr = _LIBCONFINI_CHR_CASEFOLD_(*chrptr); + } +} + + + + /* CONCRETE UTILITIES */ + + +/** + + @brief Unparsed hard left trim (left trim of + `/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t qultrim_h (char * const srcstr, const size_t offs, const IniFormat format) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Erase the previous character + FLAG_64 Erase this character + FLAG_128 Continue the loop + + */ + + register uint8_t abcd = (format.no_double_quotes ? 130 : 128) | format.no_single_quotes; + size_t idx = offs; + + do { + + abcd = !(abcd & 28) && is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + (abcd & 207) | 64 + : !(abcd & 12) && (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) ? + ( + abcd & 16 ? + (abcd & 239) | 96 + : + abcd | 64 + ) + : !(abcd & 25) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + abcd & 4 ? + (abcd & 235) | 96 + : + (abcd & 143) | 4 + ) + : !(abcd & 22) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + abcd & 8 ? + (abcd & 231) | 96 + : + (abcd & 159) | 8 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 159) ^ 16 + : + abcd & 31; + + + if (abcd & 32) { + + srcstr[idx - 1] = '\0'; + + } + + if (abcd & 64) { + + srcstr[idx] = '\0'; + + } + + idx++; + + } while (abcd & 128); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Soft left trim within an unparsed disabled entry (left trim of + `/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`) + -- does not change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t dqultrim_s (const char * const srcstr, const size_t offs, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 A new line has just begun + FLAG_64 Continue the left trim + + */ + + + register uint16_t abcd = format.no_single_quotes | + (format.no_double_quotes << 1) | + 96; + + register size_t idx = offs; + + do { + + abcd = is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + ( + abcd & 28 ? + abcd & 63 + : + abcd + ) + : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? + ( + abcd & 12 ? + (abcd & 47) | 32 + : + (abcd & 111) | 32 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + ( + abcd & 28 ? + (abcd & 31) | 16 + : + (abcd & 95) | 16 + ) + : (abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 79 + : !(abcd & 54) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 95) ^ 8 + : !(abcd & 57) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 95) ^ 4 + : srcstr[idx] ? + abcd & 31 + : + abcd & 19; + + + idx++; + + } while (abcd & 64); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Gets the position of the first occurence out of quotes of a + given character, stopping after a given number of charcters + @param str The string where to search + @param chr The character to to search + @param len The maximum number of characters to read + @param format The format of the INI file + @return The offset of the first occurence of @p chr, or @p len if + @p chr has not been not found + +**/ +static inline size_t getn_metachar_pos (const char * const str, const char chr, const size_t len, const IniFormat format) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + + */ + + for ( + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + + idx < len && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_WITH_EOL_))); + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Gets the position of the first occurence out of quotes of a + given character + @param str The string where to search + @param chr The character to to search + @param format The format of the INI file + @return The offset of the first occurence of @p chr or the length of + @p str if @p chr has not been not found + +**/ +static inline size_t get_metachar_pos (const char * const str, const char chr, const IniFormat format) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + --> As in #getn_metachar_pos() + + */ + + for ( + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + + str[idx] && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_NO_EOL_))); + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Replaces `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/` + with `"$1"` + @param srcstr The target string (it may contain multi-line + escape sequences) + @param len Length of the string + @param is_disabled The string represents a disabled entry + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t unescape_cr_lf (char * const srcstr, const size_t len, const _LIBCONFINI_CHARBOOL_ is_disabled, const IniFormat format) { + + register size_t idx_s = 0, idx_d = 0; + register uint8_t eol_i = _LIBCONFINI_EOL_IDX_; + register _LIBCONFINI_CHARBOOL_ is_escaped = _LIBCONFINI_FALSE_; + size_t probe; + + while (idx_s < len) { + + if ( + is_escaped && ( + srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i] || srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^= 1] + ) + ) { + + srcstr[idx_d - 1] = srcstr[idx_s++]; + + if (srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^ 1]) { + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + if (is_disabled) { + + probe = ltrim_s(srcstr, idx_s, _LIBCONFINI_NO_EOL_); + + if (_LIBCONFINI_IS_DIS_MARKER_(srcstr[probe], format)) { + + idx_s = probe + 1; + + } + + } + + is_escaped = _LIBCONFINI_FALSE_; + + } else { + + is_escaped = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? + !is_escaped + : + _LIBCONFINI_FALSE_; + + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + } + + for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); + + return idx_d; + +} + + +/** + + @brief Sanitizes a section path + @param secpath The section path + @param format The format of the INI file + @return The new length of the string + + Out of quotes, similar to ECMAScript + `secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")` + + A section path can start with a dot (append), but cannot end with a dot. Spaces + surrounding dots will be removed. Fragments surrounded by single or double + quotes (if these are enabled) are prevented from changes. + +**/ +static size_t sanitize_section_path (char * const secpath, const IniFormat format) { + + /* + + Mask `abcd` (12 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 These are initial spaces + FLAG_64 This is a space out of quotes + FLAG_128 This is a dot out of quotes + FLAG_256 This is anything *but* an opening single/double quote + FLAG_512 Don't ignore the last two characters + FLAG_1024 Don't overwrite the previous character + FLAG_2048 Path contains at least one name + + */ + + register uint16_t abcd = (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes; + register size_t idx_s = 0, idx_d = 0; + + for (; secpath[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = !(abcd & 12) && is_some_space(secpath[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 224 ? + (abcd & 3055) | 832 + : + (abcd & 4079) | 1856 + ) + : !(abcd & 12) && secpath[idx_s] == _LIBCONFINI_SUBSECTION_ ? + ( + abcd & (abcd & 32 ? 128 : 192) ? + (abcd & 2959) | 896 + : + (abcd & 3983) | 1920 + ) + : !(abcd & 25) && secpath[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 3839) | 1540 + : abcd & 256 ? + (abcd & 3867) | 3840 + : + (abcd & 3579) | 1280 + ) + : !(abcd & 22) && secpath[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 3839) | 1544 + : abcd & 256 ? + (abcd & 3863) | 3840 + : + (abcd & 3575) | 1280 + ) + : secpath[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 3871) | 3840) ^ 16 + : + (abcd & 3855) | 3840; + + + if (abcd & 512) { + + secpath[ + abcd & 1024 ? + idx_d++ + : idx_d ? + idx_d - 1 + : + idx_d + ] = !(~abcd & 384) ? + _LIBCONFINI_SUBSECTION_ + : !(~abcd & 320) ? + _LIBCONFINI_COLLAPSED_ + : + secpath[idx_s]; + + } else if (idx_d) { + + idx_d--; + + } + + } + + for ( + + idx_s = idx_d && (abcd & 2048) && (abcd & 192) ? + --idx_d + : + idx_d; + + secpath[idx_s]; + + secpath[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at + this stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_everything (char * const ini_string, const IniFormat format) { + + /* + + Mask `abcd` (9 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is *not* a space out of quotes + FLAG_64 This is an opening single/double quote + FLAG_128 Don't ignore this character + FLAG_256 Jump this character and the one before this + + */ + + register size_t idx_s = 0, idx_d = 0; + + register uint16_t abcd = (is_some_space(*ini_string, _LIBCONFINI_WITH_EOL_) ? 128 : 160) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 143) | 128 + : + abcd & 47 + ) + : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 239) | 196 + : abcd & 64 ? + (abcd & 299) | 256 + : + (abcd & 171) | 160 + ) + : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 239) | 200 + : abcd & 64 ? + (abcd & 295) | 256 + : + (abcd & 167) | 160 + ) + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 191) | 160) ^ 16 + : + (abcd & 175) | 160; + + + if (abcd & 256) { + + idx_d--; + + } else if (abcd & 128) { + + ini_string[idx_d++] = abcd & 44 ? ini_string[idx_s] : _LIBCONFINI_COLLAPSED_; + + } + + } + + for ( + + idx_s = !(abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/\s+/g, " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at this + stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_spaces (char * const ini_string, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is a space out of quotes + FLAG_64 Jump this character + + */ + + register uint8_t abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes; + register size_t idx_s = 0; + size_t idx_d = 0; + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 111) | 64 + : + (abcd & 47) | 32 + ) + : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 15) ^ 4 + : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 15) ^ 8 + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : + abcd & 15; + + + if (~abcd & 64) { + + ini_string[idx_d++] = abcd & 32 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx_s]; + + } + + } + + for ( + + idx_s = (abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Similar to ECMAScript `str.replace(/''|""/g, "")` + @param str The string to collapse + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_empty_quotes (char * const str, const IniFormat format) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is an opening single/double quote + FLAG_64 These are empty quotes + + */ + + register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + register size_t lshift = ltrim_s(str, 0, _LIBCONFINI_WITH_EOL_), idx = lshift; + + for (; str[idx]; idx++) { + + /* Revision #1 */ + + abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + ~abcd & 40 ? + ((abcd & 47) | 32) ^ 8 + : + (abcd & 71) | 64 + ) + : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + ~abcd & 36 ? + ((abcd & 47) | 32) ^ 4 + : + (abcd & 75) | 64 + ) + : + abcd & 15; + + + str[idx - lshift] = str[idx]; + + if (abcd & 64) { + + lshift += 2; + + } + + } + + for (idx -= lshift; str[idx]; str[idx++] = '\0'); + + return rtrim_h(str, idx - lshift, _LIBCONFINI_WITH_EOL_); + +} + + +/** + + @brief Removes all comment initializers (`#` and/or `;`) from the + beginning of each line of a comment + @param srcstr The comment to parse (it may contain multi-line + escape sequences) + @param len The length of @p srcstr + @param format The format of the INI file + @return The new length of the string + + - In multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, "$1")` + - In single-line comments: `srcstr.replace(/^[#;]+/, "")` + + The argument @p srcstr may begin with a comment initializer (`#` or `;` + depending on the format), or with the character that immediately follows it. + +**/ +static size_t uncomment (char * const srcstr, size_t len, const IniFormat format) { + + register size_t idx_s = 0, idx_d = 0; + + if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) { + + /* + + The comment can be multi-line + + */ + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Don't erase any character + FLAG_2 We are in an odd sequence of backslashes + FLAG_4 This new line character is escaped + FLAG_8 This character is a comment character and follows + `/(\n\s*|\r\s*)/` + FLAG_16 This character is a part of a group of spaces that follow + a new line (`/(\n|\r)[\t \v\f]+/`) + FLAG_32 This character is *not* a new line character (`/[\r\n]/`) + + */ + + for (register uint8_t abcd = 8; idx_s < len; idx_s++) { + + abcd = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ((abcd & 35) | 32) ^ 2 + : srcstr[idx_s] == _LIBCONFINI_LF_ || srcstr[idx_s] == _LIBCONFINI_CR_ ? + (abcd << 1) & 4 + : !(abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ? + (abcd & 40) | 8 + : !(abcd & 40) && is_some_space(srcstr[idx_s], _LIBCONFINI_NO_EOL_) ? + (abcd & 57) | 16 + : + (abcd & 33) | 32; + + + if (!(abcd & 25)) { + + srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s]; + + } else if (!(abcd & 28)) { + + idx_d++; + + } + + } + + } else { + + /* + + The comment cannot be multi-line + + */ + + for (; idx_s < len && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format); idx_s++); + + if (!idx_s) { + + return len; + + } + + for (; idx_s < len; srcstr[idx_d++] = srcstr[idx_s++]); + + } + + for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); + + return idx_d; + +} + + +/** + + @brief Tries to determine the type of a member "as if it was active" + @param srcstr String containing an individual node (it may + contain multi-line escape sequences) + @param len Length of the node + @param allow_implicit A boolean: `true` if keys without a key-value + delimiter are allowed, `false` otherwise + @param format The format of the INI file + @return The node type (see header) + +**/ +static uint8_t get_type_as_active ( + const char * const srcstr, + const size_t len, + const _LIBCONFINI_CHARBOOL_ allow_implicit, + const IniFormat format +) { + + const _LIBCONFINI_CHARBOOL_ invalid_delimiter = _LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + + if ( + !len || _LIBCONFINI_IS_ANY_MARKER_(*srcstr, format) || ( + *((unsigned char *) srcstr) == format.delimiter_symbol && !invalid_delimiter + ) + ) { + + return INI_UNKNOWN; + + } + + register uint16_t abcd; + register size_t idx; + + if (format.section_paths != INI_NO_SECTIONS && *srcstr == _LIBCONFINI_OPEN_SECTION_) { + + if (format.no_spaces_in_names) { + + /* + + Search for the CLOSE SECTION character and possible spaces in names + -- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The + algorithm is made more complex by the fact that LF and CR characters + are still escaped at this stage. + + */ + + /* + + Mask `abcd` (10 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Only one level of nesting is allowed (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 What follows cannot contain spaces + FLAG_256 Continue the loop + FLAG_512 Section path is *not* valid + + */ + + + idx = 1; + + abcd = (format.section_paths == INI_ONE_LEVEL_ONLY ? 772: 768) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + do { + + /* Revision #2 */ + + abcd = idx >= len ? + abcd & 767 + : !(abcd & 42) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 991) ^ 16 + : !(abcd & 49) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 991) ^ 8 + : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? + (abcd & 991) | 64 + : is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? + ( + ~abcd & 32 ? + abcd | 64 + : ~abcd & 192 ? + (abcd & 991) | 192 + : + (abcd & 767) | 128 + ) + : !(abcd & 28) && srcstr[idx] == _LIBCONFINI_SUBSECTION_ ? + ( + ~abcd & 224 ? + abcd & 799 + : + abcd & 767 + ) + : !(abcd & 24) && srcstr[idx] == _LIBCONFINI_CLOSE_SECTION_ ? + ( + ~abcd & 224 ? + abcd & 159 + : + abcd & 767 + ) + : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? + ( + ~abcd & 32 ? + abcd | 32 + : ~abcd & 192 ? + (abcd & 991) | 128 + : + (abcd & 735) + ) + : ~abcd & 192 ? + (abcd & 927) | 128 + : + (abcd & 671) | 128; + + + idx++; + + } while (abcd & 256); + + if (abcd & 512) { + + return INI_UNKNOWN; + + } + + } else if ((idx = getn_metachar_pos(srcstr, _LIBCONFINI_CLOSE_SECTION_, len, format) + 1) > len) { + + return INI_UNKNOWN; + + } + + /* + + Scan for possible non-space characters following the CLOSE SECTION + character: if found the node cannot represent a section path (but it can + possibly represent a key). Empty quotes surrounded by spaces will be + tolerated. + + */ + + /* + + Recycling variable `abcd` (6 bits used)...: + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Continue the loop + + */ + + abcd = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ nonspace_check: /* \ + \/ ______________________ \ */ + + + if (abcd) { + + if (idx >= len) { + + return INI_SECTION; + + } + + switch (srcstr[idx++]) { + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = abcd & 28 ? 0 : abcd & 47; + goto nonspace_check; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = abcd & 12 ? 0 : abcd & 47; + goto nonspace_check; + + case _LIBCONFINI_BACKSLASH_: + + abcd = abcd & 28 ? 0 : abcd | 16; + goto nonspace_check; + + case _LIBCONFINI_DOUBLE_QUOTES_: + + abcd = abcd & 22 ? 0 : (abcd & 47) ^ 8; + goto nonspace_check; + + case _LIBCONFINI_SINGLE_QUOTES_: + + abcd = abcd & 25 ? 0 : (abcd & 47) ^ 4; + goto nonspace_check; + + } + + } + + } + + /* + + It can be just a key... + + */ + + if (invalid_delimiter && !allow_implicit) { + + return INI_UNKNOWN; + + } + + /* + + Recycling variable `abcd` (2 bits used)...: + + FLAG_1 The delimiter **must** be present + FLAG_2 Search for spaces in names + + */ + + abcd = (format.no_spaces_in_names << 1) | (allow_implicit ? 0 : 1); + + if (abcd) { + + idx = getn_metachar_pos(srcstr, (char) format.delimiter_symbol, len, format); + + if ((abcd & 1) && idx == len) { + + return INI_UNKNOWN; + + } + + if (abcd & 2) { + + idx = urtrim_s(srcstr, idx); + + do { + + if (is_some_space(srcstr[--idx], _LIBCONFINI_WITH_EOL_)) { + + return INI_UNKNOWN; + + } + + } while (idx); + + } + + } + + return INI_KEY; + +} + + +/** + + @brief Examines a (single-/multi-line) segment and checks whether + it contains more than just one node + @param srcstr Segment to examine (it may contain multi-line + escape sequences) + @param format The format of the INI file + @return Number of entries found + +**/ +static size_t further_cuts (char * const srcstr, const IniFormat format) { + + /* + + Shared flags of mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not allow disabled entries after space (const) + FLAG_8 Formats supports multi-line entries everywhere (const) + FLAG_16 Formats supports multi-line entries everywhere except in + comments (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + + */ + + register uint16_t abcd = ((format.disabled_after_space << 2) ^ 4) | + (format.no_double_quotes << 1) | + format.no_single_quotes | ( + format.multiline_nodes == INI_MULTILINE_EVERYWHERE ? + 8 + : format.multiline_nodes == INI_BUT_COMMENTS ? + 16 + : + 0 + ); + + + register size_t idx; + size_t focus_at, unparsable_at, search_at = 0, num_entries = 0; + + + /* \ /\ + \ */ search_for_cuts: /* \ + \/ ______________________ \ */ + + + if (!srcstr[search_at]) { + + return num_entries; + + } + + unparsable_at = 0; + + abcd = _LIBCONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && ( + !(abcd & 4) || !is_some_space(srcstr[search_at + 1], _LIBCONFINI_NO_EOL_) + ) ? + (abcd & 31) | 2560 + : _LIBCONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ? + (abcd & 8 ? (abcd & 31) | 1024 : abcd & 31) + : (abcd & 8) && ( + srcstr[search_at] == _LIBCONFINI_IC_INT_MARKER_ || _LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format) + ) ? + (abcd & 31) | 3072 + : + (abcd & 31) | 2048; + + + if (abcd & 2048) { + + num_entries++; + + } + + if (abcd & 1536) { + + /* + + Node starts with `/[;#]/` and can be a disabled entry in any format, or + a simple comment or a block that must be ignored in multi-line formats + + */ + + /* + + Mask `abcd` (14 bits used): + + FLAG_256 This or the previous character was not a space + FLAG_512 We are in a disabled entry or a comment (semi-const) + FLAG_1024 We are in a simple comment or in a block that must be ignored + and format supports multi-line entries (semi-const) + FLAG_2048 We are *not* in a block that must be ignored (semi-const) + FLAG_4096 We have *not* just found an inline comment nested within a + disabled entry + FLAG_8192 We had previously found an inline comment nested in this + segment, but the entry that preceded it had been checked and + did not seem to represent a valid disabled entry + + NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at + the beginning of the function; all other flags are already set + to zero. For the meaning of flags FLAG_1-FLAG_128 see the + beginning of the function. + + */ + + idx = ltrim_s(srcstr, search_at + 1, _LIBCONFINI_NO_EOL_) - 1; + + + /* \ /\ + \ */ inactive_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + + if (~abcd & 8) { + + /* + + Check if this is a valid disabled entry. If it is not, + search for line breaks. + + If the code has reached this point it means that according + to the format disabled entries can be multi-line but + comments cannot, and #get_type_as_active() has never been + invoked on this entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if ( + srcstr[focus_at] && !get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) { + + srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; + unparsable_at = search_at + 1; + + } + + } + + break; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + /* + + Line break has been found in a multi-line disabled entry or + a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + idx = ltrim_s(srcstr, idx + 1, _LIBCONFINI_WITH_EOL_); + + if ( + abcd & 2048 ? + !( + _LIBCONFINI_IS_DIS_MARKER_(srcstr[idx], format) && (abcd & 24) && ( + (~abcd & 516) || !is_some_space(srcstr[idx + 1], _LIBCONFINI_NO_EOL_) + ) + ) && !( + _LIBCONFINI_IS_COM_MARKER_(srcstr[idx], format) && (abcd & 8) && ( + ((abcd ^ 512) & 8704) || !get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) + ) + : + !_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format) + ) { + + rtrim_h(srcstr, idx, _LIBCONFINI_WITH_EOL_); + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + /* + + No case break here, keep it like this! `case /[ \t\v\f]/` must + follow (switch case fallthrough). + + */ + __attribute__((fallthrough)); + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 15999) | 4096; + goto inactive_cut; + + case _LIBCONFINI_BACKSLASH_: + + abcd = (abcd | 4352) ^ 128; + goto inactive_cut; + + default: + + abcd = !(abcd & 1376) && (~abcd & 8200) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + (abcd & 12159) | 256 + : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 64 + : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 32 + : + (abcd & 16255) | 4352; + + + if (abcd & 4096) { + + goto inactive_cut; + + } + + if (~abcd & 8192) { + + /* + + Inline comment has been found in a (supposedly) disabled entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if (get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + )) { + + if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; + num_entries++; + + } + + srcstr[idx - 1] = '\0'; + + if (abcd & 8) { + + goto inactive_cut; + + } + + unparsable_at = idx + 1; + + } else { + + abcd |= 8192; + srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; + + if (abcd & 8) { + + goto inactive_cut; + + } + + unparsable_at = search_at + 1; + + } + + } + + /* No case break here (last case) */ + + } + + } else if (_LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) { + + /* + + Node starts with `/[;#]/` but cannot be multi-line or represent a + disabled entry + + */ + + unparsable_at = search_at + 1; + + } else { + + /* + + Node is active: search for inline comments + + */ + + /* + + Recycling variable `abcd` (11 bits used)...: + + FLAG_256 Comment marker follows an escaped new line made of only one + character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"` + or `"\\\n\r"`) + FLAG_512 This was neither a hash nor a semicolon character + FLAG_1024 This was not a space + + NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at the + beginning of the function; all other flags are already set to zero + (see previous usage of `abcd` within this function), with the only + exception of FLAG_2048, which I am going to overwrite immediately. + For the meaning of flags FLAG_1-FLAG_128 see the beginning of the + function. + + */ + + abcd = (abcd & 2047) | 1536; + idx = search_at; + + + /* \ /\ + \ */ active_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + break; + + case _LIBCONFINI_VT_: + case _LIBCONFINI_FF_: + case _LIBCONFINI_HT_: + case _LIBCONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 639) | 512; + goto active_cut; + + case _LIBCONFINI_LF_: + case _LIBCONFINI_CR_: + + abcd = (abcd & 639) | ((abcd << 1) & 256) | 512; + goto active_cut; + + case _LIBCONFINI_BACKSLASH_: + + abcd = ((abcd & 1791) | 1536) ^ 128; + goto active_cut; + + default: + + abcd = _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 1407 + : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 64 + : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 32 + : + (abcd & 1791) | 1536; + + if (abcd & 1760) { + + goto active_cut; + + } + + /* + + Inline comment has been found in an active entry. + + */ + + if (abcd & 256) { + + /* + + Remove the backslash if the comment immediately follows an + escaped new line expressed by one chararacter + (`/\\[\r\n]/`). In case of CR + LF or LF + CR + (`/\\\n\r|\\\r\n/`) the backslash will be removed later by + #strip_ini_cache(). + + */ + + srcstr[idx - 2] = '\0'; + + } + + srcstr[idx - 1] = '\0'; + + if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; + + if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + num_entries++; + + } else if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + unparsable_at = idx + 1; + /* No case break here (last case) */ + + } + + } + + if (unparsable_at) { + + /* + + Cut unparsable multi-line comments + + */ + + for (idx = unparsable_at; srcstr[idx]; idx++) { + + if (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) { + + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + } + + } + + return num_entries; + +} + +/** @startfnlist **/ + + + + /*\ + |*| + |*| GLOBAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* LIBRARY'S MAIN FUNCTIONS */ + + + /** @utility{strip_ini_cache} **/ +/** + + @brief Parses and tokenizes a buffer containing an INI file, then + dispatches its content to a custom callback + @param ini_source The buffer containing the INI file to tokenize + @param ini_length The length of @p ini_source without counting the + NUL terminator (if any -- se below) + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + The @p ini_source parameter must be a valid pointer to a buffer of size + @p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need + to be NUL-terminated, but _it does need one extra byte where to append a NUL + terminator_ -- in fact, as soon as this function is invoked, + `ini_source[ini_length]` will be immediately set to `\0`. + + In most cases, as when using `strlen()` for computing @p ini_length, this is not + a concern, since `ini_source[ini_length]` will always be `\0` by the very + definition of `strlen()`, and will only get overwritten with the same value. + However, if you are passing a substring of a string, for example the fragment + `foo=bar` of the string `foo=barracuda`, you must expect the string to be + immediately truncated into `foo=bar\0acuda`. + + In other words, @p ini_source must point to a memory location where at least + `ini_length + 1` bytes are freely usable. + + The user given function @p f_init (see #IniStatsHandler data type) will be + invoked with two arguments: `statistics` (a pointer to an #IniStatistics + structure containing some properties about the file read) and `user_data` (the + custom argument @p user_data previously passed). If @p f_init returns a non-zero + value the caller function will be interrupted. + + The user given function @p f_foreach (see #IniDispHandler data type) will be + invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure + containing the parsed member of the INI file) and `user_data` (the custom + argument @p user_data previously passed). If @p f_foreach returns a non-zero + value the caller function will be interrupted. + + After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source + parameter must be considered as a _corrupted buffer_ and should be freed or + overwritten. For more information about this function, please refer to the + @ref libconfini. + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection-safe, before dispatching the parsed + content this function will strip all `NUL` characters possibly present + in the buffer (with the exception of the last one). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_EOOR. + + @include topics/strip_ini_cache.c + +**/ +int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + const _LIBCONFINI_CHARBOOL_ valid_delimiter = !_LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + _LIBCONFINI_CHARBOOL_ tmp_bool; + register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2; + size_t tmp_size_t_1, tmp_size_t_2; + + ini_source[ini_length] = '\0'; + + /* + + PART ONE: Examine and isolate each segment + + */ + + #define __ISNT_ESCAPED__ tmp_bool + #define __LSHIFT__ tmp_fast_size_t_1 + #define __EOL_N__ tmp_fast_size_t_2 + #define __N_MEMBERS__ tmp_size_t_1 + #define __NODE_AT__ tmp_size_t_2 + + /* UTF-8 BOM */ + __LSHIFT__ = *((unsigned char *) ini_source) == 0xEF && + *((unsigned char *) ini_source + 1) == 0xBB && + *((unsigned char *) ini_source + 2) == 0xBF + ? 3 : 0; + + + for ( + + __N_MEMBERS__ = 0, + __EOL_N__ = _LIBCONFINI_EOL_IDX_, + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_, + __NODE_AT__ = 0, + idx = __LSHIFT__; + + idx < ini_length; + + idx++ + + ) { + + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + if (ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__] || ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__ ^= 1]) { + + if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) { + + ini_source[idx - __LSHIFT__] = '\0'; + __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); + __NODE_AT__ = idx - __LSHIFT__ + 1; + + } else if (ini_source[idx + 1] == _LIBCONFINI_SPACES_[__EOL_N__ ^ 1]) { + + idx++; + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + } + + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; + + } else if (ini_source[idx] == _LIBCONFINI_BACKSLASH_) { + + __ISNT_ESCAPED__ = !__ISNT_ESCAPED__; + + } else if (ini_source[idx]) { + + __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; + + } else { + + /* Remove `NUL` characters from the buffer (if any) */ + __LSHIFT__++; + + } + + } + + const size_t real_length = idx - __LSHIFT__; + + while (idx > real_length) { + + ini_source[--idx] = '\0'; + + } + + __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); + + /* Debug */ + + /* + + for (size_t tmp = 0; tmp < ini_length + 1; tmp++) { + putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]); + } + putchar('\n'); + + */ + + IniStatistics this_doc = { + .format = format, + .bytes = ini_length, + .members = __N_MEMBERS__ + }; + + if (f_init && f_init(&this_doc, user_data)) { + + return CONFINI_IINTR; + + } + + #undef __NODE_AT__ + #undef __N_MEMBERS__ + #undef __EOL_N__ + #undef __LSHIFT__ + #undef __ISNT_ESCAPED__ + + /* + + PART TWO: Dispatch the parsed input + + */ + + if (!f_foreach) { + + return CONFINI_SUCCESS; + + } + + #define __ITER__ tmp_fast_size_t_1 + #define __NODE_AT__ tmp_fast_size_t_2 + #define __PARENT_IS_DISABLED__ tmp_bool + #define __REAL_PARENT_LEN__ tmp_size_t_1 + #define __CURR_PARENT_LEN__ tmp_size_t_2 + + __REAL_PARENT_LEN__ = 0, __CURR_PARENT_LEN__ = 0; + + char + * curr_parent_str = ini_source + real_length, + * subparent_str = curr_parent_str, + * real_parent_str = curr_parent_str; + + IniDispatch dsp = { + .format = format, + .dispatch_id = 0 + }; + + __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; + + for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) { + + if (ini_source[idx]) { + + continue; + + } + + if (!ini_source[__NODE_AT__] || _LIBCONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)) { + + __NODE_AT__ = idx + 1; + continue; + + } + + if (dsp.dispatch_id >= this_doc.members) { + + return CONFINI_EOOR; + + } + + dsp.data = ini_source + __NODE_AT__; + dsp.d_len = idx - __NODE_AT__; + + /* Set `dsp.value` to an empty string */ + dsp.value = ini_source + idx; + + if ( + _LIBCONFINI_IS_DIS_MARKER_(*dsp.data, format) && ( + format.disabled_after_space || !is_some_space(dsp.data[1], _LIBCONFINI_NO_EOL_) + ) + ) { + + __ITER__ = dqultrim_s(dsp.data, 0, format); + + dsp.type = get_type_as_active( + dsp.data + __ITER__, + dsp.d_len - __ITER__, + format.disabled_can_be_implicit, + format + ); + + if (dsp.type) { + + dsp.data += __ITER__; + dsp.d_len -= __ITER__; + + /* + + // Not strictly needed... + for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0'); + + */ + + } + + dsp.type |= 4; + + } else { + + switch (*dsp.data) { + + default: + + if (!_LIBCONFINI_IS_ANY_MARKER_(*dsp.data, format)) { + + dsp.type = get_type_as_active(dsp.data, dsp.d_len, _LIBCONFINI_TRUE_, format); + break; + + } + + /* + + No case break here, keep it like this! + `case _LIBCONFINI_SC_INT_MARKER_` must follow + (switch case fallthrough). + + */ + __attribute__((fallthrough)); + + case _LIBCONFINI_SC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_COMMENT; + + break; + + case _LIBCONFINI_IC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_INLINE_COMMENT; + /* No case break here (last case) */ + + } + + } + + if (__CURR_PARENT_LEN__ && *subparent_str) { + + __ITER__ = 0; + + do { + + curr_parent_str[__ITER__ + __CURR_PARENT_LEN__] = subparent_str[__ITER__]; + + } while (subparent_str[__ITER__++]); + + __CURR_PARENT_LEN__ += __ITER__ - 1; + subparent_str = curr_parent_str + __CURR_PARENT_LEN__; + + } + + if (__PARENT_IS_DISABLED__ && !(dsp.type & 4)) { + + real_parent_str[__REAL_PARENT_LEN__] = '\0'; + __CURR_PARENT_LEN__ = __REAL_PARENT_LEN__; + curr_parent_str = real_parent_str; + __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; + + } else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) { + + __REAL_PARENT_LEN__ = __CURR_PARENT_LEN__; + real_parent_str = curr_parent_str; + __PARENT_IS_DISABLED__ = _LIBCONFINI_TRUE_; + + } + + dsp.append_to = curr_parent_str; + dsp.at_len = __CURR_PARENT_LEN__; + + if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) { + + dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format); + + } else if (format.multiline_nodes != INI_NO_MULTILINE) { + + dsp.d_len = unescape_cr_lf(dsp.data, dsp.d_len, dsp.type & 4, format); + + } + + switch (dsp.type) { + + /* + + case INI_UNKNOWN: + + // Do nothing + + break; + + */ + + case INI_SECTION: + case INI_DISABLED_SECTION: + + *dsp.data++ = '\0'; + __ITER__ = getn_metachar_pos(dsp.data, _LIBCONFINI_CLOSE_SECTION_, dsp.d_len, format); + + while (dsp.data[__ITER__]) { + + dsp.data[__ITER__++] = '\0'; + + } + + dsp.d_len = format.section_paths == INI_ONE_LEVEL_ONLY ? + collapse_everything(dsp.data, format) + : + sanitize_section_path(dsp.data, format); + + + if (format.section_paths == INI_ONE_LEVEL_ONLY || *dsp.data != _LIBCONFINI_SUBSECTION_) { + + /* + + Append to root (this is an absolute path) + + */ + + curr_parent_str = dsp.data; + __CURR_PARENT_LEN__ = dsp.d_len; + subparent_str = ini_source + idx; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if (format.section_paths == INI_ABSOLUTE_ONLY || !__CURR_PARENT_LEN__) { + + /* + + Append to root and remove the leading dot (parent is root or + relative paths are not allowed) + + */ + + curr_parent_str = ++dsp.data; + __CURR_PARENT_LEN__ = --dsp.d_len; + subparent_str = ini_source + idx; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if (dsp.d_len != 1) { + + /* + + Append to the current parent (this is a relative path + and parent is not root) + + */ + + subparent_str = dsp.data; + + } + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_KEY: + case INI_DISABLED_KEY: + + if ( + valid_delimiter && ( + __ITER__ = getn_metachar_pos(dsp.data, (char) dsp.format.delimiter_symbol, dsp.d_len, format) + ) < dsp.d_len + ) { + + dsp.data[__ITER__] = '\0'; + dsp.value = dsp.data + __ITER__ + 1; + + + switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { + + case 0: dsp.v_len = collapse_everything(dsp.value, format); break; + + case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break; + + case 2: dsp.v_len = collapse_spaces(dsp.value, format); break; + + case 4: + + dsp.value += ltrim_h(dsp.value, 0, _LIBCONFINI_WITH_EOL_); + dsp.v_len = rtrim_h(dsp.value, dsp.d_len + dsp.data - dsp.value, _LIBCONFINI_WITH_EOL_); + /* No case break here (last case) */ + + } + + } else if (format.implicit_is_not_empty) { + + dsp.value = INI_GLOBAL_IMPLICIT_VALUE; + dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN; + + } + + dsp.d_len = collapse_everything(dsp.data, format); + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_COMMENT: + case INI_INLINE_COMMENT: + + dsp.append_to = ini_source + idx; + dsp.at_len = 0; + /* No case break here (last case) */ + + } + + if (f_foreach(&dsp, user_data)) { + + return CONFINI_FEINTR; + + } + + dsp.dispatch_id++; + __NODE_AT__ = idx + 1; + + } + + #undef __CURR_PARENT_LEN__ + #undef __REAL_PARENT_LEN__ + #undef __PARENT_IS_DISABLED__ + #undef __NODE_AT__ + #undef __ITER__ + + return CONFINI_SUCCESS; + +} + + + /** @utility{load_ini_file} **/ +/** + + @brief Parses an INI file and dispatches its content to a custom + callback using a `FILE` structure as argument + @param ini_file The `FILE` handle pointing to the INI file to + parse + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + The @p ini_file parameter must be a `FILE` handle with read privileges. On some + platforms, such as Microsoft Windows, it might be needed to add the binary + specifier to the mode string (`"b"`) in order to prevent discrepancies between + the physical size of the file and its computed size: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + FILE * my_file = fopen("example.conf", "rb"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG. + + @include topics/load_ini_file.c + +**/ +int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + _LIBCONFINI_OFF_T_ file_size; + + if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { + + return CONFINI_EBADF; + + } + + if ((size_t) file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) { + + free(cache); + return CONFINI_EIO; + + } + + const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); + + free(cache); + return return_value; + +} + + + /** @utility{load_ini_path} **/ +/** + + @brief Parses an INI file and dispatches its content to a custom + callback using a path as argument + @param path The path of the INI file + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, + #CONFINI_EFBIG. + + @include topics/load_ini_path.c + +**/ +int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + FILE * const ini_file = fopen(path, "rb"); + + if (!ini_file) { + + return CONFINI_ENOENT; + + } + + _LIBCONFINI_OFF_T_ file_size; + + if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { + + return CONFINI_EBADF; + + } + + if ((size_t) file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) { + + free(cache); + return CONFINI_EIO; + + } + + /* No checks here, as there is nothing we can do about it... */ + fclose(ini_file); + + const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); + + free(cache); + return return_value; + +} + + + + /* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */ + + + /** @utility{ini_string_match_ss} **/ +/** + + @brief Compares two simple strings and checks whether they match + @param simple_string_a The first simple string + @param simple_string_b The second simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + Simple strings are user-given strings or the result of #ini_string_parse(). The + @p format argument is used for the following fields: + + - `format.case_sensitive` + +**/ +bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +) { + + register size_t idx = 0; + + if (format.case_sensitive) { + + do { + + if (simple_string_a[idx] != simple_string_b[idx]) { + + return _LIBCONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _LIBCONFINI_TRUE_; + + } + + do { + + if (_LIBCONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string_b[idx])) { + + return _LIBCONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_si} **/ +/** + + @brief Compares a simple string and an INI string and and checks + whether they match + @param ini_string The INI string escaped according to + @p format + @param simple_string The simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between a simple string + and an INI string + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_si(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the simple + string and the INI string after the latter has been parsed by + #ini_string_parse() when `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string, format); + + printf( + "%s\n", + ini_string_match_ss(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or + the result of #ini_string_parse(). + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in the INI string as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + + @include topics/ini_string_match_si.c + +**/ +bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Format supports escape sequences (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an escaped single/double quote in a format that supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint8_t abcd = INIFORMAT_HAS_NO_ESC(format) ? + 67 + : + 68 | (format.no_double_quotes << 1) | format.no_single_quotes; + + register size_t idx_i = 0; + size_t idx_s = 0, nbacksl = 0; + + + /* \ /\ + \ */ si_match: /* \ + \/ ______________________ \ */ + + + if ((abcd & 4) && ini_string[idx_i] == _LIBCONFINI_BACKSLASH_) { + + for (abcd &= 63, nbacksl++; ini_string[++idx_i] == _LIBCONFINI_BACKSLASH_; nbacksl++); + + } + + abcd = !(abcd & 10) && ini_string[idx_i] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx_i] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 24) || !is_some_space(ini_string[idx_i], _LIBCONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (nbacksl) { + + nbacksl = ((abcd & 32 ? nbacksl : nbacksl + 1) >> 1) + 1; + + while (--nbacksl) { + + if (simple_string[idx_s++] != _LIBCONFINI_BACKSLASH_) { + + return _LIBCONFINI_FALSE_; + + } + + } + + } + + if ((abcd & 128) || ((abcd & 64) && !simple_string[idx_s])) { + + idx_i++; + goto si_match; + + } + + if ( + abcd & 64 ? + simple_string[idx_s] != _LIBCONFINI_COLLAPSED_ || !simple_string[idx_s + 1] + : format.case_sensitive ? + ini_string[idx_i] != simple_string[idx_s] + : + _LIBCONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string[idx_s]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + idx_s++; + + if (ini_string[idx_i++]) { + + goto si_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_ii} **/ +/** + + @brief Compares two INI strings and checks whether they match + @param ini_string_a The first INI string unescaped according to + @p format + @param ini_string_b The second INI string unescaped according to + @p format + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between two INI strings + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the same two + INI strings after these have been parsed by #ini_string_parse() when + `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string_1, format); + ini_string_parse(my_ini_string_2, format); + + printf("%s\n", + ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + +**/ +bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +) { + + const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register uint8_t side = 1; + register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; + uint8_t abcd_pair[2]; + const char * chrptr_pair[2] = { ini_string_a, ini_string_b }; + size_t nbacksl_pair[2]; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 Skip this character + + */ + + abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ ii_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { + + for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); + + abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 4 + : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 8 + : !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? + (abcd_pair[side] & 111) | 96 + : *chrptr_pair[side] ? + abcd_pair[side] & 47 + : + abcd_pair[side] & 15; + + + if (abcd_pair[side] & 64) { + + chrptr_pair[side]++; + goto ii_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto ii_match; + + } + + turn_allowed = _LIBCONFINI_TRUE_; + + if (*nbacksl_pair || nbacksl_pair[1]) { + + if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { + + return _LIBCONFINI_FALSE_; + + } + + side = 1; + goto ii_match; + + } + + if ( + ( + abcd_pair[side ^ 1] & 32 ? + !(abcd_pair[side] & 32) + : + abcd_pair[(side ^= 1) ^ 1] & 32 + ) && *chrptr_pair[side] + ) { + + if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { + + return _LIBCONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 95; + turn_allowed = _LIBCONFINI_FALSE_; + goto ii_match; + + } + + if ( + format.case_sensitive ? + **chrptr_pair != *chrptr_pair[1] + : + _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + if (**chrptr_pair) { + + (*chrptr_pair)++; + + } + + if (*chrptr_pair[1]) { + + chrptr_pair[1]++; + + } + + if (**chrptr_pair || *chrptr_pair[1]) { + + *abcd_pair &= 95; + abcd_pair[1] &= 95; + side = 1; + goto ii_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_array_match} **/ +/** + + @brief Compares two INI arrays and checks whether they match + @param ini_string_a The first INI array + @param ini_string_b The second INI array + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A boolean: `true` if the two arrays match, `false` otherwise + + This function grants that the result of the comparison between two INI arrays + will always match the the _literal_ comparison between the individual members + of both arrays after these have been parsed, one by one, by #ini_string_parse() + (with `format.do_not_collapse_values` set to `false`). + + This function can be used, with `'.'` as delimiter, to compare section paths. + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` + +**/ +bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +) { + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + return ini_string_match_ii(ini_string_a, ini_string_b, format); + + } + + const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register uint8_t side = 1; + register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; + uint8_t abcd_pair[2]; + size_t nbacksl_pair[2]; + const char * chrptr_pair[2] = { + ini_string_a + ltrim_s(ini_string_a, 0, _LIBCONFINI_WITH_EOL_), + ini_string_b + ltrim_s(ini_string_b, 0, _LIBCONFINI_WITH_EOL_) + }; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 This is a delimiter + FLAG_128 Skip this character + + */ + + abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ delimited_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { + + for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); + + abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? + ( + delimiter || (abcd_pair[side] & 64) ? + (abcd_pair[side] & 239) | 160 + : + (abcd_pair[side] & 111) | 96 + ) + : delimiter && !(abcd_pair[side] & 12) && *chrptr_pair[side] == delimiter ? + (abcd_pair[side] & 111) | 96 + : !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 4 + : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 8 + : *chrptr_pair[side] ? + abcd_pair[side] & 47 + : delimiter ? + abcd_pair[side] & 15 + : + (abcd_pair[side] & 79) ^ 64; + + + if (abcd_pair[side] & 128) { + + chrptr_pair[side]++; + goto delimited_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto delimited_match; + + } + + turn_allowed = _LIBCONFINI_TRUE_; + + if (*nbacksl_pair || nbacksl_pair[1]) { + + if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { + + return _LIBCONFINI_FALSE_; + + } + + side = 1; + goto delimited_match; + + } + + if ((*abcd_pair ^ abcd_pair[1]) & 64) { + + return _LIBCONFINI_FALSE_; + + } + + if ( + !( + abcd_pair[side ^ 1] & 32 ? + abcd_pair[side] & 96 + : + (abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32 + ) && *chrptr_pair[side] + ) { + + if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { + + return _LIBCONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 223; + turn_allowed = _LIBCONFINI_FALSE_; + goto delimited_match; + + } + + if (~*abcd_pair & 64) { + + if ( + format.case_sensitive ? + **chrptr_pair != *chrptr_pair[1] + : + _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) + ) { + + return _LIBCONFINI_FALSE_; + + } + + *abcd_pair &= 223; + abcd_pair[1] &= 223; + + } + + if (**chrptr_pair) { + + (*chrptr_pair)++; + + } + + if (*chrptr_pair[1]) { + + chrptr_pair[1]++; + + } + + if (**chrptr_pair || *chrptr_pair[1]) { + + side = 1; + goto delimited_match; + + } + + return _LIBCONFINI_TRUE_; + +} + + + /** @utility{ini_unquote} **/ +/** + + @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes + (if single/double quotes are considered metacharacters in + respect to the format given) + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is very similar to #ini_string_parse(), except that does not + bother collapsing the sequences of more than one space that might result from + removing empty quotes. Its purpose is to be used to parse key and section names, + since these are always dispatched as already collapsed. In order to parse + values, or array parts listed in values, please use #ini_string_parse(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifiyng them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If the string does not contain quotes, or if quotes are + considered to be normal characters, no changes will be made. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` + + @include topics/ini_string_parse.c + +**/ +size_t ini_unquote (char * const ini_string, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + register size_t idx = 0; + + if (INIFORMAT_HAS_NO_ESC(format)) { + + /* + + There are no escape sequences... I will just return the length of the + string... + + */ + + while (ini_string[idx++]); + + return idx - 1; + + } + + size_t lshift = 0, nbacksl = 0; + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 This is an unescaped single quote and format supports single + quotes + FLAG_32 This is an unescaped double quote and format supports double + quotes + + */ + + for (register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; ini_string[idx]; idx++) { + + abcd = !(abcd & 6) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 47) | 32 + : !(abcd & 9) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 31) | 16 + : + abcd & 15; + + + if (!(nbacksl & 1) && (abcd & 48)) { + + abcd ^= (abcd >> 2) & 12; + lshift++; + continue; + + } + + if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if ((nbacksl & 1) && (abcd & 48)) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + if (lshift) { + + ini_string[idx - lshift] = ini_string[idx]; + + } + + } + + lshift += nbacksl >> 1; + + for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0'); + + return idx - lshift; + +} + + + /** @utility{ini_string_parse} **/ +/** + + @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes + (if single/double quotes are considered metacharacters in + respect to the format given); if the format allows it, sequences + of one or more spaces out of quotes will be collapsed + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is meant to be used to parse values. In order to parse key and + section names please use #ini_unquote(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifying them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If `format.do_not_collapse_values` is set to non-zero, spaces + surrounding empty quotes will be collapsed together with the latter. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` + - `format.do_not_collapse_values` + + @note `format.multiline_nodes` is used only to figure out whether there are + escape sequences or not. For all other purposes new line characters will + be considered to be equal to any other space character, even if the + format is not multi-line -- new line characters should never appear in + non-multi-line formats. + + @include topics/ini_string_parse.c + +**/ +size_t ini_string_parse (char * const ini_string, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an *escaped* single/double quote and format supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint8_t abcd = (format.do_not_collapse_values ? 68 : 64) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t idx, lshift; + + if (format.multiline_nodes == INI_NO_MULTILINE) { + + switch (abcd) { + + case 64 | 2 | 1 : + + /* + + There are no escape sequences, but spaces might still need to + be collapsed. + + */ + + for (idx = 0, lshift = 0; ini_string[idx]; idx++) { + + abcd = !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? 3 : abcd & 64 ? 195 : 67; + + if (abcd & 128) { + + lshift++; + + } else { + + ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; + + } + + } + + for ( + + idx -= (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return idx - lshift; + + case 64 | 4 | 2 | 1 : + + /* + + There are no escape sequences and spaces don't need to be + collapsed, but left and right trim might still be necessary. + + */ + + return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); + + } + + } + + /* + + There might be escape sequences... + + */ + + size_t nbacksl = 0; + + for (idx = lshift = 0; ini_string[idx]; idx++) { + + abcd = !(abcd & 10) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 28) || !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (abcd & 128) { + + lshift++; + continue; + + } + + if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if (abcd & 32) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; + + } + + lshift += nbacksl >> 1; + + for ( + + idx -= (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return (abcd & 28) ^ 4 ? + idx - lshift + : + rtrim_h(ini_string, idx - lshift, _LIBCONFINI_WITH_EOL_); + +} + + + /** @utility{ini_array_get_length} **/ +/** + + @brief Gets the length of a stringified INI array in number of members + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the INI array + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + +**/ +size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +) { + + if (!ini_string) { + + return 0; + + } + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + return 1; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 This is a delimiter + + */ + + register uint8_t abcd = (delimiter ? 64 : 68) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t counter = 0; + + for (size_t idx = 0; ini_string[idx]; idx++) { + + /* Revision #1 */ + + abcd = !(abcd & 28) && ini_string[idx] == delimiter ? + (abcd & 159) | 128 + : !(abcd & 24) && is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? + ( + (abcd & 68) ^ 4 ? + (abcd & 95) | 64 + : + (abcd & 223) | 192 + ) + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd & 63) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + (abcd & 31) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + (abcd & 31) ^ 8 + : + abcd & 31; + + + if (abcd & 128) { + + counter++; + + } + + } + + return !counter || (~abcd & 68) ? + counter + 1 + : + counter; + +} + + + /** @utility{ini_array_foreach} **/ +/** + + @brief Calls a custom function for each member of a stringified INI + array, without modifying the content of the buffer -- useful for + read-only (`const`) stringified arrays + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniSubstrHandler data type) will be + invoked with six arguments: `ini_string`, `memb_offset` (the offset of the + member in bytes), `memb_length` (the length of the member in bytes), `memb_num` + (the offset of the member in number of members), `format` (the format of the INI + file), `user_data` (the custom argument @p user_data previously passed). If + @p f_foreach returns a non-zero value the function #ini_array_foreach() will be + interrupted. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR. + + @include topics/ini_array_foreach.c. + +**/ +int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +) { + + if (!ini_string) { + + return CONFINI_SUCCESS; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_s(ini_string, 0, _LIBCONFINI_WITH_EOL_); + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? + CONFINI_FEINTR + : + CONFINI_SUCCESS; + + } + + register uint8_t abcd = (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + do { + + abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? + abcd & 159 + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + if ( + f_foreach( + ini_string, + offs, + rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = abcd & 128 ? idx + 1 : ltrim_s(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); + + return CONFINI_SUCCESS; + +} + + + /** @utility{ini_array_shift} **/ +/** + + @brief Shifts the location pointed by @p ini_strptr to the next member + of the INI array (without modifying the content of the buffer), + or to `NULL` if the INI array has no more members -- useful for + read-only (`const`) stringified arrays + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the array member that has been left behind + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_shift.c + +**/ +size_t ini_array_shift (const char ** const ini_strptr, const char delimiter, const IniFormat format) { + + size_t toklen = 0; + + if (*ini_strptr && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + if (!delimiter) { + + toklen = ltrim_s(*ini_strptr, 0, _LIBCONFINI_WITH_EOL_); + + } + + toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format); + *ini_strptr += toklen; + toklen = rtrim_s(*ini_strptr - toklen, toklen, _LIBCONFINI_WITH_EOL_); + + if (**ini_strptr) { + + *ini_strptr += ltrim_s(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return toklen; + + } + + } + + } + + *ini_strptr = (char *) 0; + return toklen; + +} + + + /** @utility{ini_array_collapse} **/ +/** + + @brief Compresses the distribution of the data in a stringified INI + array by removing all the white spaces that surround its + delimiters, empty quotes, collapsable spaces, etc + @param ini_string The stringified array + @param delimiter The delimiter between the array members -- + if zero (`INI_ANY_SPACE`) any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The new length of the stringified array + + Out of quotes similar to ECMAScript + `ini_string.replace(new RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`. + If #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces + (`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of + what the format says. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + This function can be useful before invoking `memcpy()` using @p ini_string as + source, when saving memory is a priority. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.do_not_collapse_values` + - `format.preserve_empty_quotes` + + Examples: + + 1. Using comma as delimiter: + - Before: ` first , second + , third , etc. ` + - After: `first,second,third,etc.` + 2. Using `INI_ANY_SPACE` as delimiter: + - Before: ` first second + third etc. ` + - After: `first second third etc.` + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_collapse.c + + @note The actual space occupied by the array might get reduced further after + each member is parsed by #ini_string_parse(). + +**/ +size_t ini_array_collapse (char * const ini_string, const char delimiter, const IniFormat format) { + + if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { + + case 0: return collapse_everything(ini_string, format); + case 1: return collapse_empty_quotes(ini_string, format); + case 2: return collapse_spaces(ini_string, format); + case 3: return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); + + } + + } + + /* + + Mask `abcd` (16 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Preserve empty quotes (const) + FLAG_16 Any space is delimiter (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + FLAG_256 This is *not* a delimiter out of quotes + FLAG_512 This is *not* a space out of quotes + FLAG_1024 These are some quotes + FLAG_2048 These are some quotes or among the last spaces are some empty + quotes + FLAG_4096 Save current `idx_d` in `fallback` + FLAG_8192 Restore `idx_d` from `fallback` before writing + FLAG_16384 Decrease `idx_d` before writing + FLAG_32768 Keep increasing `idx_d` after writing + + */ + + size_t idx_s = 0, idx_d = 0, fallback = 0; + + register uint16_t abcd = (delimiter ? 0 : 16) | + (format.preserve_empty_quotes << 3) | + (format.do_not_collapse_values << 2) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = !(abcd & 112) && ini_string[idx_s] == delimiter ? + ( + (abcd & 536) && ((abcd & 1560) ^ 8) && ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ? + (abcd & 33407) | 33280 + : + (abcd & 41599) | 41472 + ) + : !(abcd & 96) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? + ( + !((abcd & 1816) ^ 1800) ? + (abcd & 43391) | 40960 + : !(~abcd & 1560) ? + (abcd & 41087) | 40960 + : !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || !((abcd & 1560) ^ 1048) ? + (abcd & 32895) | 32768 + : !(abcd & 540) || !((abcd & 1564) ^ 8) || !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ? + abcd & 2431 + : ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ? + (abcd & 39295) | 36864 + : + (abcd & 35199) | 32768 + ) + : !(abcd & 193) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? + ( + !((abcd & 3896) ^ 8) ? + (abcd & 44927) | 44064 + : !((abcd & 3896) ^ 2056) ? + (abcd & 36735) | 36128 + : !((abcd & 1056) ^ 32) ? + (abcd & 33631) | 33536 + : !(abcd & 40) || !(~abcd & 1064) ? + ((abcd & 36735) | 35840) ^ 32 + : ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ? + (abcd & 40831) | 39968 + : + ((abcd & 20351) | 19456) ^ 32 + ) + : !(abcd & 162) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ( + !((abcd & 3928) ^ 8) ? + (abcd & 44927) | 44096 + : !((abcd & 3928) ^ 2056) ? + (abcd & 36735) | 36160 + : !((abcd & 1088) ^ 64) ? + (abcd & 33599) | 33536 + : !(abcd & 72) || !(~abcd & 1096) ? + ((abcd & 36735) | 35840) ^ 64 + : ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ? + (abcd & 40831) | 40000 + : + ((abcd & 20351) | 19456) ^ 64 + ) + : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? + ( + (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + ((abcd & 33791) | 33536) ^ 128 + : + ((abcd & 41983) | 41728) ^ 128 + ) + : (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + (abcd & 33663) | 33536 + : + (abcd & 41855) | 41728; + + + ini_string[ + abcd & 16384 ? + --idx_d + : abcd & 8192 ? + (idx_d = fallback) + : abcd & 4096 ? + (fallback = idx_d) + : + idx_d + ] = (abcd & 1636) && ((abcd & 1392) ^ 16) ? + ini_string[idx_s] + : + _LIBCONFINI_COLLAPSED_; + + + if (abcd & 32768) { + + idx_d++; + + } + + } + + for ( + + idx_s = ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ? + (idx_d = 0) + : !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || !((abcd & 1304) ^ 1032) ? + (idx_d = fallback) + : !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ? + (idx_d -= 2) + : ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ? + idx_d + : + --idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + + /** @utility{ini_array_break} **/ +/** + + @brief Replaces the first delimiter found (together with the spaces + that surround it) with `\0` + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A pointer to the remaining INI array or `NULL` if the remaining + array is empty + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will return `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_break.c + +**/ +char * ini_array_break (char * const ini_string, const char delimiter, const IniFormat format) { + + if (ini_string && !_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + char * remnant; + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + remnant = ini_string; + + while (*remnant++); + + rtrim_h(ini_string, remnant - ini_string - 1, _LIBCONFINI_WITH_EOL_); + + } else { + + remnant = ini_string + get_metachar_pos(ini_string, delimiter, format); + + if (*remnant) { + + *remnant = '\0'; + rtrim_h(ini_string, remnant - ini_string, _LIBCONFINI_WITH_EOL_); + remnant += ltrim_h(remnant, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || *remnant) { + + return remnant; + + } + + } + + } + + } + + return (char *) 0; + +} + + + /** @utility{ini_array_release} **/ +/** + + @brief Replaces the first delimiter found (together with the spaces + that surround it) with `\0`, then shifts the location pointed by + @p ini_strptr to the next member of the INI array, or to `NULL` + if the INI array has no more members + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The array member that has been released + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will set @p ini_strptr to `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_release.c + +**/ +char * ini_array_release (char ** const ini_strptr, const char delimiter, const IniFormat format) { + + char * const token = *ini_strptr; + + if (token && !_LIBCONFINI_IMPLICIT_RANGE_(token) && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + *ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format); + + if (**ini_strptr) { + + **ini_strptr = '\0'; + rtrim_h(token, *ini_strptr - token, _LIBCONFINI_WITH_EOL_); + *ini_strptr += ltrim_h(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return token; + + } + + } + + } + + *ini_strptr = (char *) 0; + return token; + +} + + + /** @utility{ini_array_split} **/ +/** + + @brief Splits a stringified INI array into NUL-separated members and + calls a custom function for each member + @param ini_string The stringified array (it cannot be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniStrHandler data type) will be + invoked with five arguments: `member` (the member of the array), `memb_length` + (the length of the member in bytes), `memb_num` (the offset of the member in + number of members), `format` (the format of the INI file), `user_data` (the + custom argument @p user_data previously passed). If @p f_foreach returns a + non-zero value the function #ini_array_split() will be interrupted. + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this + function is no-op and will return an error code. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR. + + @include topics/ini_array_split.c. + +**/ +int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +) { + + if (!ini_string || _LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { + + return CONFINI_EROADDR; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_h(ini_string, 0, _LIBCONFINI_WITH_EOL_); + + if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* + + We have no delimiters (array has only one member) + + */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? + CONFINI_FEINTR + : + CONFINI_SUCCESS; + + } + + register uint8_t abcd = (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + + do { + + abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? + abcd & 159 + : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + ini_string[idx] = '\0'; + + if ( + f_foreach( + ini_string + offs, + rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = abcd & 128 ? idx + 1 : ltrim_h(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); + + return CONFINI_SUCCESS; + +} + + + /** @utility{ini_global_set_lowercase_mode} **/ +/** + + @brief Sets the value of the global variable + #INI_GLOBAL_LOWERCASE_MODE + @param lowercase The new value + @return Nothing + + If @p lowercase is `true`, key and section names in case-insensitive INI formats + will be dispatched lowercase, verbatim otherwise (default value: `true`). + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + +**/ +void ini_global_set_lowercase_mode (const bool lowercase) { + + INI_GLOBAL_LOWERCASE_MODE = lowercase; + +} + + + /** @utility{ini_global_set_implicit_value} **/ +/** + + @brief Sets the value to be to be assigned to implicit keys + @param implicit_value The string to be used as implicit value + (usually `"YES"`, or `"TRUE"`) + @param implicit_v_len The length of @p implicit_value (usually + `0`, independently of its real length) + @return Nothing + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + + @include topics/ini_global_set_implicit_value.c + +**/ +void ini_global_set_implicit_value (char * const implicit_value, const size_t implicit_v_len) { + + INI_GLOBAL_IMPLICIT_VALUE = implicit_value; + INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len; + +} + + + /** @utility{ini_fton} **/ +/** + + @brief Calculates the #IniFormatNum of an #IniFormat + @param source The #IniFormat to compute + @return The unique unsigned integer that identifies the format given + +**/ +IniFormatNum ini_fton (const IniFormat source) { + + #define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) | + + return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0; + + #undef __INIFORMAT_ID__ + +} + + + /** @utility{ini_ntof} **/ +/** + + @brief Constructs a new #IniFormat according to an #IniFormatNum + @param format_num The #IniFormatNum to parse + @return The new #IniFormat constructed + + @note If @p format_num `>` `16777215` it will be truncated to 24 bits. + +**/ +IniFormat ini_ntof (const IniFormatNum format_num) { + + #define __MAX_1_BITS__ 1 + #define __MAX_2_BITS__ 3 + #define __MAX_3_BITS__ 7 + #define __MAX_4_BITS__ 15 + #define __MAX_5_BITS__ 31 + #define __MAX_6_BITS__ 63 + #define __MAX_7_BITS__ 127 + #define __MAX_8_BITS__ 255 + #define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \ + (unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__), + + return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) }; + + #undef __INIFORMAT_PROPERTIES__ + #undef __MAX_8_BITS__ + #undef __MAX_7_BITS__ + #undef __MAX_6_BITS__ + #undef __MAX_5_BITS__ + #undef __MAX_4_BITS__ + #undef __MAX_3_BITS__ + #undef __MAX_2_BITS__ + #undef __MAX_1_BITS__ + +} + + + /** @utility{ini_get_bool} **/ +/** + + @brief Checks whether a string matches one of the booleans listed in + the private constant #INI_BOOLEANS (case-insensitive) + @param ini_string A string to check (it can be `NULL`) + @param when_fail A value that is returned if no matching + boolean is found + @return The matching boolean (`0` or `1`) or @p when_fail if + @p ini_string does not contain a valid INI boolean + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + @include miscellanea/typed_ini.c + +**/ +int ini_get_bool (const char * const ini_string, const int when_fail) { + + if (!ini_string) { + + return when_fail; + + } + + register uint8_t bool_idx; + register size_t pair_idx, chr_idx; + + for (pair_idx = 0; pair_idx < sizeof(INI_BOOLEANS) / sizeof(const char * const [2]); pair_idx++) { + + for (bool_idx = 0; bool_idx < 2; bool_idx++) { + + chr_idx = 0; + + while (_LIBCONFINI_CHR_CASEFOLD_(ini_string[chr_idx]) == INI_BOOLEANS[pair_idx][bool_idx][chr_idx]) { + + if (!ini_string[chr_idx++]) { + + return (int) bool_idx; + + } + + } + + } + + } + + return when_fail; + +} + + + + /* LINKS - In case you don't have `#include <stdlib.h>` in your source */ + + +/** + + @alias{ini_get_int} + Pointer to + [`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi) + @alias{ini_get_lint} + Pointer to + [`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol) + @alias{ini_get_llint} + Pointer to + [`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll) + @alias{ini_get_double} + Pointer to + [`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof) + +**/ + +int (* const ini_get_int) (const char * ini_string) = atoi; + +long int (* const ini_get_lint) (const char * ini_string) = atol; + +long long int (* const ini_get_llint) (const char * ini_string) = atoll; + +double (* const ini_get_double) (const char * ini_string) = atof; + +/* Legacy support -- please **do not use this**! */ +#ifdef ini_get_float +#undef ini_get_float +#endif +double (* const ini_get_float) (const char * ini_string) = atof; + + + + /* GLOBAL VARIABLES */ + + +bool INI_GLOBAL_LOWERCASE_MODE = _LIBCONFINI_FALSE_; + +char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0; + +size_t INI_GLOBAL_IMPLICIT_V_LEN = 0; + + + +/** @endfnlist **/ + +/* EOF */ + diff --git a/vendor/confini.h b/vendor/confini.h new file mode 100644 index 0000000..c4c0068 --- /dev/null +++ b/vendor/confini.h @@ -0,0 +1,547 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ + +/** + + @file confini.h + @brief libconfini header + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.14.0 + @date 2016-2020 + @see https://madmurphy.github.io/libconfini + +**/ + + +#ifndef _LIBCONFINI_HEADER_ +#define _LIBCONFINI_HEADER_ + + + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> + + + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* PRIVATE (HEADER-SCOPED) MACROS */ + + +#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \ + unsigned char NAME:SIZE; +#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL, +#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0, +#define _LIBCONFINI_INIFORMAT_TYPE_ \ + struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) } +#define _LIBCONFINI_DEFAULT_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) } +#define _LIBCONFINI_UNIXLIKE_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) } + + + +/* PUBLIC MACROS */ + + +/** + @brief Calls a user-given macro (that accepts four arguments) for each row + of the table +**/ +/* + NOTE: The following table and the order of its rows **define** (and link + together) both the #IniFormat and #IniFormatNum data types declared in this + header +*/ +#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\ + + NAME BIT SIZE DEFAULT + */\ + _____( delimiter_symbol, 0, 7, INI_EQUALS ) \ + _____( case_sensitive, 7, 1, false )/* + */\ + _____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \ + _____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \ + _____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \ + _____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/* + */\ + _____( no_single_quotes, 16, 1, false ) \ + _____( no_double_quotes, 17, 1, false ) \ + _____( no_spaces_in_names, 18, 1, false ) \ + _____( implicit_is_not_empty, 19, 1, false ) \ + _____( do_not_collapse_values, 20, 1, false ) \ + _____( preserve_empty_quotes, 21, 1, false ) \ + _____( disabled_after_space, 22, 1, false ) \ + _____( disabled_can_be_implicit, 23, 1, false ) + + + +/** + @brief Checks whether a format does **not** support escape sequences +**/ +#define INIFORMAT_HAS_NO_ESC(FORMAT) \ + (FORMAT.multiline_nodes == INI_NO_MULTILINE && \ + FORMAT.no_double_quotes && FORMAT.no_single_quotes) + + + +/* PUBLIC TYPEDEFS */ + + +/** + @brief 24-bit bitfield representing the format of an INI file (INI + dialect) +**/ +typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat; + + +/** + @brief Global statistics about an INI file +**/ +typedef struct IniStatistics { + const IniFormat format; + const size_t bytes; + const size_t members; +} IniStatistics; + + +/** + @brief Dispatch of a single INI node +**/ +typedef struct IniDispatch { + const IniFormat format; + uint8_t type; + char * data; + char * value; + const char * append_to; + size_t d_len; + size_t v_len; + size_t at_len; + size_t dispatch_id; +} IniDispatch; + + +/** + @brief The unique ID of an INI format (24-bit maximum) +**/ +typedef uint32_t IniFormatNum; + + +/** + @brief Callback function for handling an #IniStatistics structure +**/ +typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data +); + + +/** + @brief Callback function for handling an #IniDispatch structure +**/ +typedef int (* IniDispHandler) ( + IniDispatch * dispatch, + void * user_data +); + + +/** + @brief Callback function for handling an INI string belonging to a + sequence of INI strings +**/ +typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data +); + + +/** + @brief Callback function for handling a selected fragment of an INI string +**/ +typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data +); + + + +/* PUBLIC FUNCTIONS */ + + +extern int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +); + + +extern bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +); + + +extern bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +); + + +extern bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_unquote ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_string_parse ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +); + + +extern size_t ini_array_shift ( + const char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_array_collapse ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_break ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_release ( + char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +); + + +extern void ini_global_set_lowercase_mode ( + const bool lowercase +); + + +extern void ini_global_set_implicit_value ( + char * const implicit_value, + const size_t implicit_v_len +); + + +extern IniFormatNum ini_fton ( + const IniFormat format +); + + +extern IniFormat ini_ntof ( + const IniFormatNum format_id +); + + +extern int ini_get_bool ( + const char * const ini_string, + const int when_fail +); + + + +/* PUBLIC LINKS */ + + +extern int (* const ini_get_int) ( + const char * ini_string +); + + +extern long int (* const ini_get_lint) ( + const char * ini_string +); + + +extern long long int (* const ini_get_llint) ( + const char * ini_string +); + + +extern double (* const ini_get_double) ( + const char * ini_string +); + + +/** + @brief Legacy support, soon to be replaced with a `float` data type -- + please **do not use `ini_get_float()`!** +**/ +#define ini_get_float \ + _Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \ + ini_get_double + + + +/* PUBLIC CONSTANTS AND VARIABLES */ + + +/** + @brief Error mask (flags not present in user-generated interruptions) +**/ +#define CONFINI_ERROR 252 + + +/** + @brief Error codes +**/ +enum ConfiniInterruptNo { + CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything + went well [value=0] **/ + CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()` + [value=1] **/ + CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()` + [value=2] **/ + CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/ + CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/ + CONFINI_EIO = 6, /**< Error reading the file [value=6] **/ + CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than + expected [value=7] **/ + CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream + [value=8] **/ + CONFINI_EFBIG = 9, /**< File too large [value=9] **/ + CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/ +}; + + +/** + @brief INI node types +**/ +enum IniNodeType { + INI_UNKNOWN = 0, /**< This is a node impossible to categorize + [value=0] **/ + INI_VALUE = 1, /**< Not used by **libconfini** (values are + dispatched together with keys) -- but + available for user's implementations + [value=1] **/ + INI_KEY = 2, /**< This is a key [value=2] **/ + INI_SECTION = 3, /**< This is a section or a section path + [value=3] **/ + INI_COMMENT = 4, /**< This is a comment [value=4] **/ + INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/ + INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/ + INI_DISABLED_SECTION = 7 /**< This is a disabled section path + [value=7] **/ +}; + + +/** + @brief Common array and key-value delimiters (but a delimiter may also be + any other ASCII character not present in this list) +**/ +enum IniDelimiters { + INI_ANY_SPACE = 0, /**< In multi-line INIs: + `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in + non-multi-line INIs: `/[\t \v\f])+/` **/ + INI_EQUALS = '=', /**< Equals character (`=`) **/ + INI_COLON = ':', /**< Colon character (`:`) **/ + INI_DOT = '.', /**< Dot character (`.`) **/ + INI_COMMA = ',' /**< Comma character (`,`) **/ +}; + + +/** + @brief Possible values of #IniFormat::semicolon_marker and + #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in + respect to a format) +**/ +enum IniCommentMarker { + INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a + disabled entry **/ + INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/ + INI_IGNORE = 2, /**< This marker opens a comment that has + been marked for deletion and must not + be dispatched or counted **/ + INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a + normal character instead **/ +}; + + +/** + @brief Possible values of #IniFormat::section_paths +**/ +enum IniSectionPaths { + INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot + express nesting to the current parent, + to root otherwise **/ + INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will + be cleaned of their leading dot and + appended to root **/ + INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot + does not express nesting and is not a + meta-character **/ + INI_NO_SECTIONS = 3 /**< Format does *not* support sections -- + `/\[[^\]]*\]/g`, if any, will be + treated as keys! **/ +}; + + +/** + @brief Possible values of #IniFormat::multiline_nodes +**/ +enum IniMultiline { + INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys + -- disabled or not -- are allowed + to be multi-line **/ + INI_BUT_COMMENTS = 1, /**< Only section paths and keys -- + disabled or not -- are allowed to + be multi-line **/ + INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and + active keys are allowed to be + multi-line **/ + INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are + disabled **/ +}; + + +/** + @brief A model format for standard INI files +**/ +static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_; + + +/** + @brief A model format for Unix-like .conf files (where space characters + are delimiters between keys and values) +**/ +/* All fields are set to `0` here. */ +static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_; + + +/** + @brief If set to `true`, key and section names in case-insensitive INI + formats will be dispatched lowercase, verbatim otherwise (default + value: `false`) +**/ +extern bool INI_GLOBAL_LOWERCASE_MODE; + + +/** + @brief Value to be assigned to implicit keys (default value: `NULL`) +**/ +extern char * INI_GLOBAL_IMPLICIT_VALUE; + + +/** + @brief Length of the value assigned to implicit keys (default value: `0`) +**/ +extern size_t INI_GLOBAL_IMPLICIT_V_LEN; + + + +/* CLEAN THE PRIVATE ENVIRONMENT */ + + +#undef _LIBCONFINI_UNIXLIKE_FORMAT_ +#undef _LIBCONFINI_DEFAULT_FORMAT_ +#undef _LIBCONFINI_INIFORMAT_TYPE_ +#undef __INIFORMAT_TABLE_CB_ZERO__ +#undef __INIFORMAT_TABLE_CB_DEFAULT__ +#undef __INIFORMAT_TABLE_CB_FIELDS__ + + + +/* END OF `_LIBCONFINI_HEADER_` */ + + +#ifdef __cplusplus +} +#endif + + +#endif + + + +/* EOF */ + diff --git a/vendor/mongoose.h b/vendor/mongoose.h index aac725e..7808e91 100644 --- a/vendor/mongoose.h +++ b/vendor/mongoose.h @@ -1867,7 +1867,7 @@ typedef struct { void cs_md5_init(cs_md5_ctx *c); void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len); -void cs_md5_final(unsigned char digest[16], cs_md5_ctx *c); +void cs_md5_final(unsigned char *md, cs_md5_ctx *c); #ifdef __cplusplus } diff --git a/vendor/toml.c b/vendor/toml.c deleted file mode 100644 index 98f765b..0000000 --- a/vendor/toml.c +++ /dev/null @@ -1,2274 +0,0 @@ -/* - - MIT License - - Copyright (c) 2017 - 2019 CK Tan - https://github.com/cktan/tomlc99 - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - -*/ -#define _POSIX_C_SOURCE 200809L -#include <stdio.h> -#include <stdlib.h> -#include <assert.h> -#include <errno.h> -#include <stdint.h> -#include <ctype.h> -#include <string.h> -#include <stdbool.h> -#include "toml.h" - - -static void* (*ppmalloc)(size_t) = malloc; -static void (*ppfree)(void*) = free; - -void toml_set_memutil(void* (*xxmalloc)(size_t), - void (*xxfree)(void*)) -{ - if (xxmalloc) ppmalloc = xxmalloc; - if (xxfree) ppfree = xxfree; -} - - -#define MALLOC(a) ppmalloc(a) -#define FREE(a) ppfree(a) - -static void* CALLOC(size_t nmemb, size_t sz) -{ - int nb = sz * nmemb; - void* p = MALLOC(nb); - if (p) { - memset(p, 0, nb); - } - return p; -} - - -static char* STRDUP(const char* s) -{ - int len = strlen(s); - char* p = MALLOC(len+1); - if (p) { - memcpy(p, s, len); - p[len] = 0; - } - return p; -} - -static char* STRNDUP(const char* s, size_t n) -{ - size_t len = strnlen(s, n); - char* p = MALLOC(len+1); - if (p) { - memcpy(p, s, len); - p[len] = 0; - } - return p; -} - - - -/** - * Convert a char in utf8 into UCS, and store it in *ret. - * Return #bytes consumed or -1 on failure. - */ -int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret) -{ - const unsigned char* buf = (const unsigned char*) orig; - unsigned i = *buf++; - int64_t v; - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (0 == (i >> 7)) { - if (len < 1) return -1; - v = i; - return *ret = v, 1; - } - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (0x6 == (i >> 5)) { - if (len < 2) return -1; - v = i & 0x1f; - for (int j = 0; j < 1; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (0xE == (i >> 4)) { - if (len < 3) return -1; - v = i & 0x0F; - for (int j = 0; j < 2; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x1E == (i >> 3)) { - if (len < 4) return -1; - v = i & 0x07; - for (int j = 0; j < 3; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x3E == (i >> 2)) { - if (len < 5) return -1; - v = i & 0x03; - for (int j = 0; j < 4; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x7e == (i >> 1)) { - if (len < 6) return -1; - v = i & 0x01; - for (int j = 0; j < 5; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - return -1; -} - - -/** - * Convert a UCS char to utf8 code, and return it in buf. - * Return #bytes used in buf to encode the char, or - * -1 on error. - */ -int toml_ucs_to_utf8(int64_t code, char buf[6]) -{ - /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */ - /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well - * as 0xfffe and 0xffff (UCS noncharacters) should not appear in - * conforming UTF-8 streams. - */ - if (0xd800 <= code && code <= 0xdfff) return -1; - if (0xfffe <= code && code <= 0xffff) return -1; - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (code < 0) return -1; - if (code <= 0x7F) { - buf[0] = (unsigned char) code; - return 1; - } - - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (code <= 0x000007FF) { - buf[0] = 0xc0 | (code >> 6); - buf[1] = 0x80 | (code & 0x3f); - return 2; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x0000FFFF) { - buf[0] = 0xe0 | (code >> 12); - buf[1] = 0x80 | ((code >> 6) & 0x3f); - buf[2] = 0x80 | (code & 0x3f); - return 3; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x001FFFFF) { - buf[0] = 0xf0 | (code >> 18); - buf[1] = 0x80 | ((code >> 12) & 0x3f); - buf[2] = 0x80 | ((code >> 6) & 0x3f); - buf[3] = 0x80 | (code & 0x3f); - return 4; - } - - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x03FFFFFF) { - buf[0] = 0xf8 | (code >> 24); - buf[1] = 0x80 | ((code >> 18) & 0x3f); - buf[2] = 0x80 | ((code >> 12) & 0x3f); - buf[3] = 0x80 | ((code >> 6) & 0x3f); - buf[4] = 0x80 | (code & 0x3f); - return 5; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x7FFFFFFF) { - buf[0] = 0xfc | (code >> 30); - buf[1] = 0x80 | ((code >> 24) & 0x3f); - buf[2] = 0x80 | ((code >> 18) & 0x3f); - buf[3] = 0x80 | ((code >> 12) & 0x3f); - buf[4] = 0x80 | ((code >> 6) & 0x3f); - buf[5] = 0x80 | (code & 0x3f); - return 6; - } - - return -1; -} - -/* - * TOML has 3 data structures: value, array, table. - * Each of them can have identification key. - */ -typedef struct toml_keyval_t toml_keyval_t; -struct toml_keyval_t { - const char* key; /* key to this value */ - const char* val; /* the raw value */ -}; - - -struct toml_array_t { - const char* key; /* key to this array */ - int kind; /* element kind: 'v'alue, 'a'rray, or 't'able */ - int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */ - - int nelem; /* number of elements */ - union { - char** val; - toml_array_t** arr; - toml_table_t** tab; - } u; -}; - - -struct toml_table_t { - const char* key; /* key to this table */ - bool implicit; /* table was created implicitly */ - - /* key-values in the table */ - int nkval; - toml_keyval_t** kval; - - /* arrays in the table */ - int narr; - toml_array_t** arr; - - /* tables in the table */ - int ntab; - toml_table_t** tab; -}; - - -static inline void xfree(const void* x) { if (x) FREE((void*)(intptr_t)x); } - - -enum tokentype_t { - INVALID, - DOT, - COMMA, - EQUAL, - LBRACE, - RBRACE, - NEWLINE, - LBRACKET, - RBRACKET, - STRING, -}; -typedef enum tokentype_t tokentype_t; - -typedef struct token_t token_t; -struct token_t { - tokentype_t tok; - int lineno; - char* ptr; /* points into context->start */ - int len; - int eof; -}; - - -typedef struct context_t context_t; -struct context_t { - char* start; - char* stop; - char* errbuf; - int errbufsz; - - token_t tok; - toml_table_t* root; - toml_table_t* curtab; - - struct { - int top; - char* key[10]; - token_t tok[10]; - } tpath; - -}; - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#define FLINE __FILE__ ":" TOSTRING(__LINE__) - -static int next_token(context_t* ctx, int dotisspecial); - -/* - Error reporting. Call when an error is detected. Always return -1. -*/ -static int e_outofmemory(context_t* ctx, const char* fline) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); - return -1; -} - - -static int e_internal(context_t* ctx, const char* fline) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); - return -1; -} - -static int e_syntax(context_t* ctx, int lineno, const char* msg) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); - return -1; -} - -static int e_badkey(context_t* ctx, int lineno) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); - return -1; -} - -static int e_keyexists(context_t* ctx, int lineno) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); - return -1; -} - -static void* expand(void* p, int sz, int newsz) -{ - void* s = MALLOC(newsz); - if (!s) return 0; - - memcpy(s, p, sz); - FREE(p); - return s; -} - -static void** expand_ptrarr(void** p, int n) -{ - void** s = MALLOC((n+1) * sizeof(void*)); - if (!s) return 0; - - s[n] = 0; - memcpy(s, p, n * sizeof(void*)); - FREE(p); - return s; -} - - -static char* norm_lit_str(const char* src, int srclen, - int multiline, - char* errbuf, int errbufsz) -{ - char* dst = 0; /* will write to dst[] and return it */ - int max = 0; /* max size of dst[] */ - int off = 0; /* cur offset in dst[] */ - const char* sp = src; - const char* sq = src + srclen; - int ch; - - /* scan forward on src */ - for (;;) { - if (off >= max - 10) { /* have some slack for misc stuff */ - int newmax = max + 50; - char* x = expand(dst, max, newmax); - if (!x) { - xfree(dst); - snprintf(errbuf, errbufsz, "out of memory"); - return 0; - } - dst = x; - max = newmax; - } - - /* finished? */ - if (sp >= sq) break; - - ch = *sp++; - /* control characters other than tab is not allowed */ - if ((0 <= ch && ch <= 0x08) - || (0x0a <= ch && ch <= 0x1f) - || (ch == 0x7f)) { - if (! (multiline && (ch == '\r' || ch == '\n'))) { - xfree(dst); - snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); - return 0; - } - } - - // a plain copy suffice - dst[off++] = ch; - } - - dst[off++] = 0; - return dst; -} - - - - -/* - * Convert src to raw unescaped utf-8 string. - * Returns NULL if error with errmsg in errbuf. - */ -static char* norm_basic_str(const char* src, int srclen, - int multiline, - char* errbuf, int errbufsz) -{ - char* dst = 0; /* will write to dst[] and return it */ - int max = 0; /* max size of dst[] */ - int off = 0; /* cur offset in dst[] */ - const char* sp = src; - const char* sq = src + srclen; - int ch; - - /* scan forward on src */ - for (;;) { - if (off >= max - 10) { /* have some slack for misc stuff */ - int newmax = max + 50; - char* x = expand(dst, max, newmax); - if (!x) { - xfree(dst); - snprintf(errbuf, errbufsz, "out of memory"); - return 0; - } - dst = x; - max = newmax; - } - - /* finished? */ - if (sp >= sq) break; - - ch = *sp++; - if (ch != '\\') { - /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */ - if ((0 <= ch && ch <= 0x08) - || (0x0a <= ch && ch <= 0x1f) - || (ch == 0x7f)) { - if (! (multiline && (ch == '\r' || ch == '\n'))) { - xfree(dst); - snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); - return 0; - } - } - - // a plain copy suffice - dst[off++] = ch; - continue; - } - - /* ch was backslash. we expect the escape char. */ - if (sp >= sq) { - snprintf(errbuf, errbufsz, "last backslash is invalid"); - xfree(dst); - return 0; - } - - /* for multi-line, we want to kill line-ending-backslash ... */ - if (multiline) { - - // if there is only whitespace after the backslash ... - if (sp[strspn(sp, " \t\r")] == '\n') { - /* skip all the following whitespaces */ - sp += strspn(sp, " \t\r\n"); - continue; - } - } - - /* get the escaped char */ - ch = *sp++; - switch (ch) { - case 'u': case 'U': - { - int64_t ucs = 0; - int nhex = (ch == 'u' ? 4 : 8); - for (int i = 0; i < nhex; i++) { - if (sp >= sq) { - snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); - xfree(dst); - return 0; - } - ch = *sp++; - int v = ('0' <= ch && ch <= '9') - ? ch - '0' - : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); - if (-1 == v) { - snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); - xfree(dst); - return 0; - } - ucs = ucs * 16 + v; - } - int n = toml_ucs_to_utf8(ucs, &dst[off]); - if (-1 == n) { - snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); - xfree(dst); - return 0; - } - off += n; - } - continue; - - case 'b': ch = '\b'; break; - case 't': ch = '\t'; break; - case 'n': ch = '\n'; break; - case 'f': ch = '\f'; break; - case 'r': ch = '\r'; break; - case '"': ch = '"'; break; - case '\\': ch = '\\'; break; - default: - snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); - xfree(dst); - return 0; - } - - dst[off++] = ch; - } - - // Cap with NUL and return it. - dst[off++] = 0; - return dst; -} - - -/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ -static char* normalize_key(context_t* ctx, token_t strtok) -{ - const char* sp = strtok.ptr; - const char* sq = strtok.ptr + strtok.len; - int lineno = strtok.lineno; - char* ret; - int ch = *sp; - char ebuf[80]; - - /* handle quoted string */ - if (ch == '\'' || ch == '\"') { - /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ - int multiline = 0; - if (sp[1] == ch && sp[2] == ch) { - sp += 3, sq -= 3; - multiline = 1; - } - else - sp++, sq--; - - if (ch == '\'') { - /* for single quote, take it verbatim. */ - if (! (ret = STRNDUP(sp, sq - sp))) { - e_outofmemory(ctx, FLINE); - return 0; - } - } else { - /* for double quote, we need to normalize */ - ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); - if (!ret) { - e_syntax(ctx, lineno, ebuf); - return 0; - } - } - - /* newlines are not allowed in keys */ - if (strchr(ret, '\n')) { - xfree(ret); - e_badkey(ctx, lineno); - return 0; - } - return ret; - } - - /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ - const char* xp; - for (xp = sp; xp != sq; xp++) { - int k = *xp; - if (isalnum(k)) continue; - if (k == '_' || k == '-') continue; - e_badkey(ctx, lineno); - return 0; - } - - /* dup and return it */ - if (! (ret = STRNDUP(sp, sq - sp))) { - e_outofmemory(ctx, FLINE); - return 0; - } - return ret; -} - - -/* - * Look up key in tab. Return 0 if not found, or - * 'v'alue, 'a'rray or 't'able depending on the element. - */ -static int check_key(toml_table_t* tab, const char* key, - toml_keyval_t** ret_val, - toml_array_t** ret_arr, - toml_table_t** ret_tab) -{ - int i; - void* dummy; - - if (!ret_tab) ret_tab = (toml_table_t**) &dummy; - if (!ret_arr) ret_arr = (toml_array_t**) &dummy; - if (!ret_val) ret_val = (toml_keyval_t**) &dummy; - - *ret_tab = 0; *ret_arr = 0; *ret_val = 0; - - for (i = 0; i < tab->nkval; i++) { - if (0 == strcmp(key, tab->kval[i]->key)) { - *ret_val = tab->kval[i]; - return 'v'; - } - } - for (i = 0; i < tab->narr; i++) { - if (0 == strcmp(key, tab->arr[i]->key)) { - *ret_arr = tab->arr[i]; - return 'a'; - } - } - for (i = 0; i < tab->ntab; i++) { - if (0 == strcmp(key, tab->tab[i]->key)) { - *ret_tab = tab->tab[i]; - return 't'; - } - } - return 0; -} - - -static int key_kind(toml_table_t* tab, const char* key) -{ - return check_key(tab, key, 0, 0, 0); -} - -/* Create a keyval in the table. - */ -static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out. */ - toml_keyval_t* dest = 0; - if (key_kind(tab, newkey)) { - xfree(newkey); - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* make a new entry */ - int n = tab->nkval; - toml_keyval_t** base; - if (0 == (base = (toml_keyval_t**) expand_ptrarr((void**)tab->kval, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->kval = base; - - if (0 == (base[n] = (toml_keyval_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - dest = tab->kval[tab->nkval++]; - - /* save the key in the new value struct */ - dest->key = newkey; - return dest; -} - - -/* Create a table in the table. - */ -static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out */ - toml_table_t* dest = 0; - if (check_key(tab, newkey, 0, 0, &dest)) { - xfree(newkey); /* don't need this anymore */ - - /* special case: if table exists, but was created implicitly ... */ - if (dest && dest->implicit) { - /* we make it explicit now, and simply return it. */ - dest->implicit = false; - return dest; - } - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* create a new table entry */ - int n = tab->ntab; - toml_table_t** base; - if (0 == (base = (toml_table_t**) expand_ptrarr((void**)tab->tab, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->tab = base; - - if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - dest = tab->tab[tab->ntab++]; - - /* save the key in the new table struct */ - dest->key = newkey; - return dest; -} - - -/* Create an array in the table. - */ -static toml_array_t* create_keyarray_in_table(context_t* ctx, - toml_table_t* tab, - token_t keytok, - char kind) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out */ - if (key_kind(tab, newkey)) { - xfree(newkey); /* don't need this anymore */ - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* make a new array entry */ - int n = tab->narr; - toml_array_t** base; - if (0 == (base = (toml_array_t**) expand_ptrarr((void**)tab->arr, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->arr = base; - - if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - toml_array_t* dest = tab->arr[tab->narr++]; - - /* save the key in the new array struct */ - dest->key = newkey; - dest->kind = kind; - return dest; -} - -/* Create an array in an array - */ -static toml_array_t* create_array_in_array(context_t* ctx, - toml_array_t* parent) -{ - const int n = parent->nelem; - toml_array_t** base; - if (0 == (base = (toml_array_t**) expand_ptrarr((void**)parent->u.arr, n))) { - e_outofmemory(ctx, FLINE); - return 0; - } - parent->u.arr = base; - parent->nelem++; - - if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { - e_outofmemory(ctx, FLINE); - return 0; - } - - return parent->u.arr[n]; -} - -/* Create a table in an array - */ -static toml_table_t* create_table_in_array(context_t* ctx, - toml_array_t* parent) -{ - int n = parent->nelem; - toml_table_t** base; - if (0 == (base = (toml_table_t**) expand_ptrarr((void**)parent->u.tab, n))) { - e_outofmemory(ctx, FLINE); - return 0; - } - parent->u.tab = base; - - if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { - e_outofmemory(ctx, FLINE); - return 0; - } - - return parent->u.tab[parent->nelem++]; -} - - -static int skip_newlines(context_t* ctx, int isdotspecial) -{ - while (ctx->tok.tok == NEWLINE) { - if (next_token(ctx, isdotspecial)) return -1; - if (ctx->tok.eof) break; - } - return 0; -} - - -static int parse_keyval(context_t* ctx, toml_table_t* tab); - -static inline int eat_token(context_t* ctx, tokentype_t typ, int isdotspecial, const char* fline) -{ - if (ctx->tok.tok != typ) - return e_internal(ctx, fline); - - if (next_token(ctx, isdotspecial)) - return -1; - - return 0; -} - - - -/* We are at '{ ... }'. - * Parse the table. - */ -static int parse_table(context_t* ctx, toml_table_t* tab) -{ - if (eat_token(ctx, LBRACE, 1, FLINE)) - return -1; - - for (;;) { - if (ctx->tok.tok == NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); - - /* until } */ - if (ctx->tok.tok == RBRACE) - break; - - if (ctx->tok.tok != STRING) - return e_syntax(ctx, ctx->tok.lineno, "expect a string"); - - if (parse_keyval(ctx, tab)) - return -1; - - if (ctx->tok.tok == NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); - - /* on comma, continue to scan for next keyval */ - if (ctx->tok.tok == COMMA) { - if (eat_token(ctx, COMMA, 1, FLINE)) - return -1; - continue; - } - break; - } - - if (eat_token(ctx, RBRACE, 1, FLINE)) - return -1; - return 0; -} - -static int valtype(const char* val) -{ - toml_timestamp_t ts; - if (*val == '\'' || *val == '"') return 's'; - if (0 == toml_rtob(val, 0)) return 'b'; - if (0 == toml_rtoi(val, 0)) return 'i'; - if (0 == toml_rtod(val, 0)) return 'd'; - if (0 == toml_rtots(val, &ts)) { - if (ts.year && ts.hour) return 'T'; /* timestamp */ - if (ts.year) return 'D'; /* date */ - return 't'; /* time */ - } - return 'u'; /* unknown */ -} - - -/* We are at '[...]' */ -static int parse_array(context_t* ctx, toml_array_t* arr) -{ - if (eat_token(ctx, LBRACKET, 0, FLINE)) return -1; - - for (;;) { - if (skip_newlines(ctx, 0)) return -1; - - /* until ] */ - if (ctx->tok.tok == RBRACKET) break; - - switch (ctx->tok.tok) { - case STRING: - { - char* val = ctx->tok.ptr; - int vlen = ctx->tok.len; - - /* set array kind if this will be the first entry */ - if (arr->kind == 0) arr->kind = 'v'; - /* check array kind */ - if (arr->kind != 'v') - return e_syntax(ctx, ctx->tok.lineno, "a string array can only contain strings"); - - /* make a new value in array */ - char** tmp = (char**) expand_ptrarr((void**)arr->u.val, arr->nelem); - if (!tmp) - return e_outofmemory(ctx, FLINE); - - arr->u.val = tmp; - if (! (val = STRNDUP(val, vlen))) - return e_outofmemory(ctx, FLINE); - - arr->u.val[arr->nelem++] = val; - - /* set array type if this is the first entry, or check that the types matched. */ - if (arr->nelem == 1) - arr->type = valtype(arr->u.val[0]); - else if (arr->type != valtype(val)) { - return e_syntax(ctx, ctx->tok.lineno, - "array type mismatch while processing array of values"); - } - - if (eat_token(ctx, STRING, 0, FLINE)) return -1; - break; - } - - case LBRACKET: - { /* [ [array], [array] ... ] */ - /* set the array kind if this will be the first entry */ - if (arr->kind == 0) arr->kind = 'a'; - /* check array kind */ - if (arr->kind != 'a') { - return e_syntax(ctx, ctx->tok.lineno, - "array type mismatch while processing array of arrays"); - } - toml_array_t* subarr = create_array_in_array(ctx, arr); - if (!subarr) return -1; - if (parse_array(ctx, subarr)) return -1; - break; - } - - case LBRACE: - { /* [ {table}, {table} ... ] */ - /* set the array kind if this will be the first entry */ - if (arr->kind == 0) arr->kind = 't'; - /* check array kind */ - if (arr->kind != 't') { - return e_syntax(ctx, ctx->tok.lineno, - "array type mismatch while processing array of tables"); - } - toml_table_t* subtab = create_table_in_array(ctx, arr); - if (!subtab) return -1; - if (parse_table(ctx, subtab)) return -1; - break; - } - - default: - return e_syntax(ctx, ctx->tok.lineno, "syntax error"); - } - - if (skip_newlines(ctx, 0)) return -1; - - /* on comma, continue to scan for next element */ - if (ctx->tok.tok == COMMA) { - if (eat_token(ctx, COMMA, 0, FLINE)) return -1; - continue; - } - break; - } - - if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; - return 0; -} - - -/* handle lines like these: - key = "value" - key = [ array ] - key = { table } -*/ -static int parse_keyval(context_t* ctx, toml_table_t* tab) -{ - token_t key = ctx->tok; - if (eat_token(ctx, STRING, 1, FLINE)) return -1; - - if (ctx->tok.tok == DOT) { - /* handle inline dotted key. - e.g. - physical.color = "orange" - physical.shape = "round" - */ - toml_table_t* subtab = 0; - { - char* subtabstr = normalize_key(ctx, key); - subtab = toml_table_in(tab, subtabstr); - xfree(subtabstr); - } - if (!subtab) { - subtab = create_keytable_in_table(ctx, tab, key); - if (!subtab) return -1; - } - if (next_token(ctx, 1)) return -1; - if (parse_keyval(ctx, subtab)) return -1; - return 0; - } - - if (ctx->tok.tok != EQUAL) { - return e_syntax(ctx, ctx->tok.lineno, "missing ="); - } - - if (next_token(ctx, 0)) return -1; - - switch (ctx->tok.tok) { - case STRING: - { /* key = "value" */ - toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key); - if (!keyval) return -1; - token_t val = ctx->tok; - - assert(keyval->val == 0); - if (! (keyval->val = STRNDUP(val.ptr, val.len))) - return e_outofmemory(ctx, FLINE); - - if (next_token(ctx, 1)) return -1; - - return 0; - } - - case LBRACKET: - { /* key = [ array ] */ - toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0); - if (!arr) return -1; - if (parse_array(ctx, arr)) return -1; - return 0; - } - - case LBRACE: - { /* key = { table } */ - toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key); - if (!nxttab) return -1; - if (parse_table(ctx, nxttab)) return -1; - return 0; - } - - default: - return e_syntax(ctx, ctx->tok.lineno, "syntax error"); - } - return 0; -} - - -typedef struct tabpath_t tabpath_t; -struct tabpath_t { - int cnt; - token_t key[10]; -}; - -/* at [x.y.z] or [[x.y.z]] - * Scan forward and fill tabpath until it enters ] or ]] - * There will be at least one entry on return. - */ -static int fill_tabpath(context_t* ctx) -{ - int lineno = ctx->tok.lineno; - int i; - - /* clear tpath */ - for (i = 0; i < ctx->tpath.top; i++) { - char** p = &ctx->tpath.key[i]; - xfree(*p); - *p = 0; - } - ctx->tpath.top = 0; - - for (;;) { - if (ctx->tpath.top >= 10) - return e_syntax(ctx, lineno, "table path is too deep; max allowed is 10."); - - if (ctx->tok.tok != STRING) - return e_syntax(ctx, lineno, "invalid or missing key"); - - char* key = normalize_key(ctx, ctx->tok); - if (!key) return -1; - ctx->tpath.tok[ctx->tpath.top] = ctx->tok; - ctx->tpath.key[ctx->tpath.top] = key; - ctx->tpath.top++; - - if (next_token(ctx, 1)) return -1; - - if (ctx->tok.tok == RBRACKET) break; - - if (ctx->tok.tok != DOT) - return e_syntax(ctx, lineno, "invalid key"); - - if (next_token(ctx, 1)) return -1; - } - - if (ctx->tpath.top <= 0) - return e_syntax(ctx, lineno, "empty table selector"); - - return 0; -} - - -/* Walk tabpath from the root, and create new tables on the way. - * Sets ctx->curtab to the final table. - */ -static int walk_tabpath(context_t* ctx) -{ - /* start from root */ - toml_table_t* curtab = ctx->root; - - for (int i = 0; i < ctx->tpath.top; i++) { - const char* key = ctx->tpath.key[i]; - - toml_keyval_t* nextval = 0; - toml_array_t* nextarr = 0; - toml_table_t* nexttab = 0; - switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { - case 't': - /* found a table. nexttab is where we will go next. */ - break; - - case 'a': - /* found an array. nexttab is the last table in the array. */ - if (nextarr->kind != 't') - return e_internal(ctx, FLINE); - - if (nextarr->nelem == 0) - return e_internal(ctx, FLINE); - - nexttab = nextarr->u.tab[nextarr->nelem-1]; - break; - - case 'v': - return e_keyexists(ctx, ctx->tpath.tok[i].lineno); - - default: - { /* Not found. Let's create an implicit table. */ - int n = curtab->ntab; - toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)curtab->tab, n); - if (0 == base) - return e_outofmemory(ctx, FLINE); - - curtab->tab = base; - - if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) - return e_outofmemory(ctx, FLINE); - - if (0 == (base[n]->key = STRDUP(key))) - return e_outofmemory(ctx, FLINE); - - nexttab = curtab->tab[curtab->ntab++]; - - /* tabs created by walk_tabpath are considered implicit */ - nexttab->implicit = true; - } - break; - } - - /* switch to next tab */ - curtab = nexttab; - } - - /* save it */ - ctx->curtab = curtab; - - return 0; -} - - -/* handle lines like [x.y.z] or [[x.y.z]] */ -static int parse_select(context_t* ctx) -{ - assert(ctx->tok.tok == LBRACKET); - - /* true if [[ */ - int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); - /* need to detect '[[' on our own because next_token() will skip whitespace, - and '[ [' would be taken as '[[', which is wrong. */ - - /* eat [ or [[ */ - if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; - if (llb) { - assert(ctx->tok.tok == LBRACKET); - if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; - } - - if (fill_tabpath(ctx)) return -1; - - /* For [x.y.z] or [[x.y.z]], remove z from tpath. - */ - token_t z = ctx->tpath.tok[ctx->tpath.top-1]; - xfree(ctx->tpath.key[ctx->tpath.top-1]); - ctx->tpath.top--; - - /* set up ctx->curtab */ - if (walk_tabpath(ctx)) return -1; - - if (! llb) { - /* [x.y.z] -> create z = {} in x.y */ - toml_table_t* curtab = create_keytable_in_table(ctx, ctx->curtab, z); - if (!curtab) return -1; - ctx->curtab = curtab; - } else { - /* [[x.y.z]] -> create z = [] in x.y */ - toml_array_t* arr = 0; - { - char* zstr = normalize_key(ctx, z); - if (!zstr) return -1; - arr = toml_array_in(ctx->curtab, zstr); - xfree(zstr); - } - if (!arr) { - arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); - if (!arr) return -1; - } - if (arr->kind != 't') - return e_syntax(ctx, z.lineno, "array mismatch"); - - /* add to z[] */ - toml_table_t* dest; - { - int n = arr->nelem; - toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)arr->u.tab, n); - if (0 == base) - return e_outofmemory(ctx, FLINE); - - arr->u.tab = base; - - if (0 == (base[n] = CALLOC(1, sizeof(*base[n])))) - return e_outofmemory(ctx, FLINE); - - if (0 == (base[n]->key = STRDUP("__anon__"))) - return e_outofmemory(ctx, FLINE); - - dest = arr->u.tab[arr->nelem++]; - } - - ctx->curtab = dest; - } - - if (ctx->tok.tok != RBRACKET) { - return e_syntax(ctx, ctx->tok.lineno, "expects ]"); - } - if (llb) { - if (! (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { - return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); - } - if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; - } - - if (eat_token(ctx, RBRACKET, 1, FLINE)) - return -1; - - if (ctx->tok.tok != NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); - - return 0; -} - - - - -toml_table_t* toml_parse(char* conf, - char* errbuf, - int errbufsz) -{ - context_t ctx; - - // clear errbuf - if (errbufsz <= 0) errbufsz = 0; - if (errbufsz > 0) errbuf[0] = 0; - - // init context - memset(&ctx, 0, sizeof(ctx)); - ctx.start = conf; - ctx.stop = ctx.start + strlen(conf); - ctx.errbuf = errbuf; - ctx.errbufsz = errbufsz; - - // start with an artificial newline of length 0 - ctx.tok.tok = NEWLINE; - ctx.tok.lineno = 1; - ctx.tok.ptr = conf; - ctx.tok.len = 0; - - // make a root table - if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { - e_outofmemory(&ctx, FLINE); - // Do not goto fail, root table not set up yet - return 0; - } - - // set root as default table - ctx.curtab = ctx.root; - - /* Scan forward until EOF */ - for (token_t tok = ctx.tok; ! tok.eof ; tok = ctx.tok) { - switch (tok.tok) { - - case NEWLINE: - if (next_token(&ctx, 1)) goto fail; - break; - - case STRING: - if (parse_keyval(&ctx, ctx.curtab)) goto fail; - - if (ctx.tok.tok != NEWLINE) { - e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); - goto fail; - } - - if (eat_token(&ctx, NEWLINE, 1, FLINE)) goto fail; - break; - - case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ - if (parse_select(&ctx)) goto fail; - break; - - default: - e_syntax(&ctx, tok.lineno, "syntax error"); - goto fail; - } - } - - /* success */ - for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); - return ctx.root; - -fail: - // Something bad has happened. Free resources and return error. - for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); - toml_free(ctx.root); - return 0; -} - - -toml_table_t* toml_parse_file(FILE* fp, - char* errbuf, - int errbufsz) -{ - int bufsz = 0; - char* buf = 0; - int off = 0; - - /* read from fp into buf */ - while (! feof(fp)) { - - if (off == bufsz) { - int xsz = bufsz + 1000; - char* x = expand(buf, bufsz, xsz); - if (!x) { - snprintf(errbuf, errbufsz, "out of memory"); - xfree(buf); - return 0; - } - buf = x; - bufsz = xsz; - } - - errno = 0; - int n = fread(buf + off, 1, bufsz - off, fp); - if (ferror(fp)) { - snprintf(errbuf, errbufsz, "%s", - errno ? strerror(errno) : "Error reading file"); - xfree(buf); - return 0; - } - off += n; - } - - /* tag on a NUL to cap the string */ - if (off == bufsz) { - int xsz = bufsz + 1; - char* x = expand(buf, bufsz, xsz); - if (!x) { - snprintf(errbuf, errbufsz, "out of memory"); - xfree(buf); - return 0; - } - buf = x; - bufsz = xsz; - } - buf[off] = 0; - - /* parse it, cleanup and finish */ - toml_table_t* ret = toml_parse(buf, errbuf, errbufsz); - xfree(buf); - return ret; -} - - -static void xfree_kval(toml_keyval_t* p) -{ - if (!p) return; - xfree(p->key); - xfree(p->val); - xfree(p); -} - -static void xfree_tab(toml_table_t* p); - -static void xfree_arr(toml_array_t* p) -{ - if (!p) return; - - xfree(p->key); - switch (p->kind) { - case 'v': - for (int i = 0; i < p->nelem; i++) xfree(p->u.val[i]); - xfree(p->u.val); - break; - - case 'a': - for (int i = 0; i < p->nelem; i++) xfree_arr(p->u.arr[i]); - xfree(p->u.arr); - break; - - case 't': - for (int i = 0; i < p->nelem; i++) xfree_tab(p->u.tab[i]); - xfree(p->u.tab); - break; - } - - xfree(p); -} - - -static void xfree_tab(toml_table_t* p) -{ - int i; - - if (!p) return; - - xfree(p->key); - - for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]); - xfree(p->kval); - - for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]); - xfree(p->arr); - - for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]); - xfree(p->tab); - - xfree(p); -} - - -void toml_free(toml_table_t* tab) -{ - xfree_tab(tab); -} - - -static void set_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len) -{ - token_t t; - t.tok = tok; - t.lineno = lineno; - t.ptr = ptr; - t.len = len; - t.eof = 0; - ctx->tok = t; -} - -static void set_eof(context_t* ctx, int lineno) -{ - set_token(ctx, NEWLINE, lineno, ctx->stop, 0); - ctx->tok.eof = 1; -} - - -/* Scan p for n digits compositing entirely of [0-9] */ -static int scan_digits(const char* p, int n) -{ - int ret = 0; - for ( ; n > 0 && isdigit(*p); n--, p++) { - ret = 10 * ret + (*p - '0'); - } - return n ? -1 : ret; -} - -static int scan_date(const char* p, int* YY, int* MM, int* DD) -{ - int year, month, day; - year = scan_digits(p, 4); - month = (year >= 0 && p[4] == '-') ? scan_digits(p+5, 2) : -1; - day = (month >= 0 && p[7] == '-') ? scan_digits(p+8, 2) : -1; - if (YY) *YY = year; - if (MM) *MM = month; - if (DD) *DD = day; - return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; -} - -static int scan_time(const char* p, int* hh, int* mm, int* ss) -{ - int hour, minute, second; - hour = scan_digits(p, 2); - minute = (hour >= 0 && p[2] == ':') ? scan_digits(p+3, 2) : -1; - second = (minute >= 0 && p[5] == ':') ? scan_digits(p+6, 2) : -1; - if (hh) *hh = hour; - if (mm) *mm = minute; - if (ss) *ss = second; - return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; -} - - -static int scan_string(context_t* ctx, char* p, int lineno, int dotisspecial) -{ - char* orig = p; - if (0 == strncmp(p, "'''", 3)) { - p = strstr(p + 3, "'''"); - if (0 == p) { - return e_syntax(ctx, lineno, "unterminated triple-s-quote"); - } - - set_token(ctx, STRING, lineno, orig, p + 3 - orig); - return 0; - } - - if (0 == strncmp(p, "\"\"\"", 3)) { - int hexreq = 0; /* #hex required */ - int escape = 0; - int qcnt = 0; /* count quote */ - for (p += 3; *p && qcnt < 3; p++) { - if (escape) { - escape = 0; - if (strchr("btnfr\"\\", *p)) continue; - if (*p == 'u') { hexreq = 4; continue; } - if (*p == 'U') { hexreq = 8; continue; } - if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */ - return e_syntax(ctx, lineno, "bad escape char"); - } - if (hexreq) { - hexreq--; - if (strchr("0123456789ABCDEF", *p)) continue; - return e_syntax(ctx, lineno, "expect hex char"); - } - if (*p == '\\') { escape = 1; continue; } - qcnt = (*p == '"') ? qcnt + 1 : 0; - } - if (qcnt != 3) { - return e_syntax(ctx, lineno, "unterminated triple-quote"); - } - - set_token(ctx, STRING, lineno, orig, p - orig); - return 0; - } - - if ('\'' == *p) { - for (p++; *p && *p != '\n' && *p != '\''; p++); - if (*p != '\'') { - return e_syntax(ctx, lineno, "unterminated s-quote"); - } - - set_token(ctx, STRING, lineno, orig, p + 1 - orig); - return 0; - } - - if ('\"' == *p) { - int hexreq = 0; /* #hex required */ - int escape = 0; - for (p++; *p; p++) { - if (escape) { - escape = 0; - if (strchr("btnfr\"\\", *p)) continue; - if (*p == 'u') { hexreq = 4; continue; } - if (*p == 'U') { hexreq = 8; continue; } - return e_syntax(ctx, lineno, "bad escape char"); - } - if (hexreq) { - hexreq--; - if (strchr("0123456789ABCDEF", *p)) continue; - return e_syntax(ctx, lineno, "expect hex char"); - } - if (*p == '\\') { escape = 1; continue; } - if (*p == '\n') break; - if (*p == '"') break; - } - if (*p != '"') { - return e_syntax(ctx, lineno, "unterminated quote"); - } - - set_token(ctx, STRING, lineno, orig, p + 1 - orig); - return 0; - } - - /* check for timestamp without quotes */ - if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { - // forward thru the timestamp - for ( ; strchr("0123456789.:+-T Z", toupper(*p)); p++); - // squeeze out any spaces at end of string - for ( ; p[-1] == ' '; p--); - // tokenize - set_token(ctx, STRING, lineno, orig, p - orig); - return 0; - } - - /* literals */ - for ( ; *p && *p != '\n'; p++) { - int ch = *p; - if (ch == '.' && dotisspecial) break; - if ('A' <= ch && ch <= 'Z') continue; - if ('a' <= ch && ch <= 'z') continue; - if (strchr("0123456789+-_.", ch)) continue; - break; - } - - set_token(ctx, STRING, lineno, orig, p - orig); - return 0; -} - - -static int next_token(context_t* ctx, int dotisspecial) -{ - int lineno = ctx->tok.lineno; - char* p = ctx->tok.ptr; - int i; - - /* eat this tok */ - for (i = 0; i < ctx->tok.len; i++) { - if (*p++ == '\n') - lineno++; - } - - /* make next tok */ - while (p < ctx->stop) { - /* skip comment. stop just before the \n. */ - if (*p == '#') { - for (p++; p < ctx->stop && *p != '\n'; p++); - continue; - } - - if (dotisspecial && *p == '.') { - set_token(ctx, DOT, lineno, p, 1); - return 0; - } - - switch (*p) { - case ',': set_token(ctx, COMMA, lineno, p, 1); return 0; - case '=': set_token(ctx, EQUAL, lineno, p, 1); return 0; - case '{': set_token(ctx, LBRACE, lineno, p, 1); return 0; - case '}': set_token(ctx, RBRACE, lineno, p, 1); return 0; - case '[': set_token(ctx, LBRACKET, lineno, p, 1); return 0; - case ']': set_token(ctx, RBRACKET, lineno, p, 1); return 0; - case '\n': set_token(ctx, NEWLINE, lineno, p, 1); return 0; - case '\r': case ' ': case '\t': - /* ignore white spaces */ - p++; - continue; - } - - return scan_string(ctx, p, lineno, dotisspecial); - } - - set_eof(ctx, lineno); - return 0; -} - - -const char* toml_key_in(const toml_table_t* tab, int keyidx) -{ - if (keyidx < tab->nkval) return tab->kval[keyidx]->key; - - keyidx -= tab->nkval; - if (keyidx < tab->narr) return tab->arr[keyidx]->key; - - keyidx -= tab->narr; - if (keyidx < tab->ntab) return tab->tab[keyidx]->key; - - return 0; -} - -toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->nkval; i++) { - if (0 == strcmp(key, tab->kval[i]->key)) - return tab->kval[i]->val; - } - return 0; -} - -toml_array_t* toml_array_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->narr; i++) { - if (0 == strcmp(key, tab->arr[i]->key)) - return tab->arr[i]; - } - return 0; -} - - -toml_table_t* toml_table_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->ntab; i++) { - if (0 == strcmp(key, tab->tab[i]->key)) - return tab->tab[i]; - } - return 0; -} - -toml_raw_t toml_raw_at(const toml_array_t* arr, int idx) -{ - if (arr->kind != 'v') - return 0; - if (! (0 <= idx && idx < arr->nelem)) - return 0; - return arr->u.val[idx]; -} - -char toml_array_kind(const toml_array_t* arr) -{ - return arr->kind; -} - -char toml_array_type(const toml_array_t* arr) -{ - if (arr->kind != 'v') - return 0; - - if (arr->nelem == 0) - return 0; - - return arr->type; -} - - -int toml_array_nelem(const toml_array_t* arr) -{ - return arr->nelem; -} - -const char* toml_array_key(const toml_array_t* arr) -{ - return arr ? arr->key : (const char*) NULL; -} - -int toml_table_nkval(const toml_table_t* tab) -{ - return tab->nkval; -} - -int toml_table_narr(const toml_table_t* tab) -{ - return tab->narr; -} - -int toml_table_ntab(const toml_table_t* tab) -{ - return tab->ntab; -} - -const char* toml_table_key(const toml_table_t* tab) -{ - return tab ? tab->key : (const char*) NULL; -} - -toml_array_t* toml_array_at(const toml_array_t* arr, int idx) -{ - if (arr->kind != 'a') - return 0; - if (! (0 <= idx && idx < arr->nelem)) - return 0; - return arr->u.arr[idx]; -} - -toml_table_t* toml_table_at(const toml_array_t* arr, int idx) -{ - if (arr->kind != 't') - return 0; - if (! (0 <= idx && idx < arr->nelem)) - return 0; - return arr->u.tab[idx]; -} - - -int toml_rtots(toml_raw_t src_, toml_timestamp_t* ret) -{ - if (! src_) return -1; - - const char* p = src_; - int must_parse_time = 0; - - memset(ret, 0, sizeof(*ret)); - - int* year = &ret->__buffer.year; - int* month = &ret->__buffer.month; - int* day = &ret->__buffer.day; - int* hour = &ret->__buffer.hour; - int* minute = &ret->__buffer.minute; - int* second = &ret->__buffer.second; - int* millisec = &ret->__buffer.millisec; - - /* parse date YYYY-MM-DD */ - if (0 == scan_date(p, year, month, day)) { - ret->year = year; - ret->month = month; - ret->day = day; - - p += 10; - if (*p) { - // parse the T or space separator - if (*p != 'T' && *p != ' ') return -1; - must_parse_time = 1; - p++; - } - } - - /* parse time HH:MM:SS */ - if (0 == scan_time(p, hour, minute, second)) { - ret->hour = hour; - ret->minute = minute; - ret->second = second; - - /* optionally, parse millisec */ - p += 8; - if (*p == '.') { - char* qq; - p++; - errno = 0; - *millisec = strtol(p, &qq, 0); - if (errno) { - return -1; - } - while (*millisec > 999) { - *millisec /= 10; - } - - ret->millisec = millisec; - p = qq; - } - - if (*p) { - /* parse and copy Z */ - char* z = ret->__buffer.z; - ret->z = z; - if (*p == 'Z' || *p == 'z') { - *z++ = 'Z'; p++; - *z = 0; - - } else if (*p == '+' || *p == '-') { - *z++ = *p++; - - if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; - *z++ = *p++; - *z++ = *p++; - - if (*p == ':') { - *z++ = *p++; - - if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; - *z++ = *p++; - *z++ = *p++; - } - - *z = 0; - } - } - } - if (*p != 0) - return -1; - - if (must_parse_time && !ret->hour) - return -1; - - return 0; -} - - -/* Raw to boolean */ -int toml_rtob(toml_raw_t src, int* ret_) -{ - if (!src) return -1; - int dummy; - int* ret = ret_ ? ret_ : &dummy; - - if (0 == strcmp(src, "true")) { - *ret = 1; - return 0; - } - if (0 == strcmp(src, "false")) { - *ret = 0; - return 0; - } - return -1; -} - - -/* Raw to integer */ -int toml_rtoi(toml_raw_t src, int64_t* ret_) -{ - if (!src) return -1; - - char buf[100]; - char* p = buf; - char* q = p + sizeof(buf); - const char* s = src; - int base = 0; - int64_t dummy; - int64_t* ret = ret_ ? ret_ : &dummy; - - - /* allow +/- */ - if (s[0] == '+' || s[0] == '-') - *p++ = *s++; - - /* disallow +_100 */ - if (s[0] == '_') - return -1; - - /* if 0 ... */ - if ('0' == s[0]) { - switch (s[1]) { - case 'x': base = 16; s += 2; break; - case 'o': base = 8; s += 2; break; - case 'b': base = 2; s += 2; break; - case '\0': return *ret = 0, 0; - default: - /* ensure no other digits after it */ - if (s[1]) return -1; - } - } - - /* just strip underscores and pass to strtoll */ - while (*s && p < q) { - int ch = *s++; - switch (ch) { - case '_': - // disallow '__' - if (s[0] == '_') return -1; - continue; /* skip _ */ - default: - break; - } - *p++ = ch; - } - if (*s || p == q) return -1; - - /* last char cannot be '_' */ - if (s[-1] == '_') return -1; - - /* cap with NUL */ - *p = 0; - - /* Run strtoll on buf to get the integer */ - char* endp; - errno = 0; - *ret = strtoll(buf, &endp, base); - return (errno || *endp) ? -1 : 0; -} - - -int toml_rtod_ex(toml_raw_t src, double* ret_, char* buf, int buflen) -{ - if (!src) return -1; - - char* p = buf; - char* q = p + buflen; - const char* s = src; - double dummy; - double* ret = ret_ ? ret_ : &dummy; - - - /* allow +/- */ - if (s[0] == '+' || s[0] == '-') - *p++ = *s++; - - /* disallow +_1.00 */ - if (s[0] == '_') - return -1; - - /* disallow +.99 */ - if (s[0] == '.') - return -1; - - /* zero must be followed by . or 'e', or NUL */ - if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) - return -1; - - /* just strip underscores and pass to strtod */ - while (*s && p < q) { - int ch = *s++; - switch (ch) { - case '.': - if (s[-2] == '_') return -1; - if (s[0] == '_') return -1; - break; - case '_': - // disallow '__' - if (s[0] == '_') return -1; - continue; /* skip _ */ - default: - break; - } - *p++ = ch; - } - if (*s || p == q) return -1; /* reached end of string or buffer is full? */ - - /* last char cannot be '_' */ - if (s[-1] == '_') return -1; - - if (p != buf && p[-1] == '.') - return -1; /* no trailing zero */ - - /* cap with NUL */ - *p = 0; - - /* Run strtod on buf to get the value */ - char* endp; - errno = 0; - *ret = strtod(buf, &endp); - return (errno || *endp) ? -1 : 0; -} - -int toml_rtod(toml_raw_t src, double* ret_) -{ - char buf[100]; - return toml_rtod_ex(src, ret_, buf, sizeof(buf)); -} - - - - -int toml_rtos(toml_raw_t src, char** ret) -{ - int multiline = 0; - const char* sp; - const char* sq; - - *ret = 0; - if (!src) return -1; - - int qchar = src[0]; - int srclen = strlen(src); - if (! (qchar == '\'' || qchar == '"')) { - return -1; - } - - // triple quotes? - if (qchar == src[1] && qchar == src[2]) { - multiline = 1; - sp = src + 3; - sq = src + srclen - 3; - /* last 3 chars in src must be qchar */ - if (! (sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) - return -1; - - /* skip new line immediate after qchar */ - if (sp[0] == '\n') - sp++; - else if (sp[0] == '\r' && sp[1] == '\n') - sp += 2; - - } else { - sp = src + 1; - sq = src + srclen - 1; - /* last char in src must be qchar */ - if (! (sp <= sq && *sq == qchar)) - return -1; - } - - if (qchar == '\'') { - *ret = norm_lit_str(sp, sq - sp, - multiline, - 0, 0); - } else { - *ret = norm_basic_str(sp, sq - sp, - multiline, - 0, 0); - } - - return *ret ? 0 : -1; -} - - -toml_datum_t toml_string_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_at(arr, idx); - if (raw) { - ret.ok = (0 == toml_rtos(raw, &ret.u.s)); - } - return ret; -} - -toml_datum_t toml_bool_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_at(arr, idx); - if (raw) { - ret.ok = (0 == toml_rtob(raw, &ret.u.b)); - } - return ret; -} - -toml_datum_t toml_int_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_at(arr, idx); - if (raw) { - ret.ok = (0 == toml_rtoi(raw, &ret.u.i)); - } - return ret; -} - -toml_datum_t toml_double_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_at(arr, idx); - if (raw) { - ret.ok = (0 == toml_rtod(raw, &ret.u.d)); - } - return ret; -} - -toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx) -{ - toml_timestamp_t ts; - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_at(arr, idx); - if (raw) { - ret.ok = (0 == toml_rtots(raw, &ts)); - if (ret.ok) { - ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); - if (ret.ok) { - *ret.u.ts = ts; - } - } - } - return ret; -} - -toml_datum_t toml_string_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtos(raw, &ret.u.s)); - } - return ret; -} - -toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtob(raw, &ret.u.b)); - } - return ret; -} - -toml_datum_t toml_int_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtoi(raw, &ret.u.i)); - } - return ret; -} - -toml_datum_t toml_double_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtod(raw, &ret.u.d)); - } - return ret; -} - -toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key) -{ - toml_timestamp_t ts; - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtots(raw, &ts)); - if (ret.ok) { - ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); - if (ret.ok) { - *ret.u.ts = ts; - } - } - } - return ret; -} diff --git a/vendor/toml.h b/vendor/toml.h deleted file mode 100644 index 19f6f64..0000000 --- a/vendor/toml.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - MIT License - - Copyright (c) 2017 - 2019 CK Tan - https://github.com/cktan/tomlc99 - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ -#ifndef TOML_H -#define TOML_H - - -#include <stdio.h> -#include <stdint.h> - - -#ifdef __cplusplus -#define TOML_EXTERN extern "C" -#else -#define TOML_EXTERN extern -#endif - -typedef struct toml_timestamp_t toml_timestamp_t; -typedef struct toml_table_t toml_table_t; -typedef struct toml_array_t toml_array_t; -typedef struct toml_datum_t toml_datum_t; - -/* Parse a file. Return a table on success, or 0 otherwise. - * Caller must toml_free(the-return-value) after use. - */ -TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, - char* errbuf, - int errbufsz); - -/* Parse a string containing the full config. - * Return a table on success, or 0 otherwise. - * Caller must toml_free(the-return-value) after use. - */ -TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ - char* errbuf, - int errbufsz); - -/* Free the table returned by toml_parse() or toml_parse_file(). Once - * this function is called, any handles accessed through this tab - * directly or indirectly are no longer valid. - */ -TOML_EXTERN void toml_free(toml_table_t* tab); - - -/* Timestamp types. The year, month, day, hour, minute, second, z - * fields may be NULL if they are not relevant. e.g. In a DATE - * type, the hour, minute, second and z fields will be NULLs. - */ -struct toml_timestamp_t { - struct { /* internal. do not use. */ - int year, month, day; - int hour, minute, second, millisec; - char z[10]; - } __buffer; - int *year, *month, *day; - int *hour, *minute, *second, *millisec; - char* z; -}; - - -/*----------------------------------------------------------------- - * Enhanced access methods - */ -struct toml_datum_t { - int ok; - union { - toml_timestamp_t* ts; /* ts must be freed after use */ - char* s; /* string value. s must be freed after use */ - int b; /* bool value */ - int64_t i; /* int value */ - double d; /* double value */ - } u; -}; - -/* on arrays: */ -/* ... retrieve size of array. */ -TOML_EXTERN int toml_array_nelem(const toml_array_t* arr); -/* ... retrieve values using index. */ -TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx); -/* ... retrieve array or table using index. */ -TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx); - -/* on tables: */ -/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ -TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx); -/* ... retrieve values using key. */ -TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key); -/* .. retrieve array or table using key. */ -TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab, - const char* key); -TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab, - const char* key); - -/*----------------------------------------------------------------- - * lesser used - */ -/* Return the array kind: 't'able, 'a'rray, 'v'alue */ -TOML_EXTERN char toml_array_kind(const toml_array_t* arr); - -/* For array kind 'v'alue, return the type of values - i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp - 0 if unknown -*/ -TOML_EXTERN char toml_array_type(const toml_array_t* arr); - -/* Return the key of an array */ -TOML_EXTERN const char* toml_array_key(const toml_array_t* arr); - -/* Return the number of key-values in a table */ -TOML_EXTERN int toml_table_nkval(const toml_table_t* tab); - -/* Return the number of arrays in a table */ -TOML_EXTERN int toml_table_narr(const toml_table_t* tab); - -/* Return the number of sub-tables in a table */ -TOML_EXTERN int toml_table_ntab(const toml_table_t* tab); - -/* Return the key of a table*/ -TOML_EXTERN const char* toml_table_key(const toml_table_t* tab); - -/*-------------------------------------------------------------- - * misc - */ -TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); -TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); -TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), - void (*xxfree)(void*)); - - -/*-------------------------------------------------------------- - * deprecated - */ -/* A raw value, must be processed by toml_rto* before using. */ -typedef const char* toml_raw_t; -TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key); -TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx); -TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret); -TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret); -TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret); -TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret); -TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen); -TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret); - - -#endif /* TOML_H */