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(&timestamp);
+                    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(&timestamp);
-                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&eacute;
+	@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: `&nbsp;first&nbsp;&nbsp; ,&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;
+	     ,&nbsp;&nbsp; third&nbsp;&nbsp; ,&nbsp; etc.&nbsp;&nbsp;`
+	   - After: `first,second,third,etc.`
+	2. Using `INI_ANY_SPACE` as delimiter:
+	   - Before: `&nbsp;&nbsp;first&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;&nbsp;
+	     third&nbsp;&nbsp;&nbsp;&nbsp; etc.&nbsp;&nbsp;&nbsp;`
+	   - 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&eacute;
+    @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 */