diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..1e0f5e6
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,2 @@
+---
+Checks: 'clang-diagnostics-*,clang-analyzer-*,linuxkernel-*,modernize-*,readability-*,-readability-magic-numbers,portability-*'
diff --git a/.drone.yml b/.drone.yml
index ce40462..3174f1e 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,9 +53,8 @@ steps:
     api_key:
       from_secret: gitea_token
     base_url: https://git.serguzim.me
-    files:
-    - build/core
-    - build/core.ini
+    title: ${DRONE_TAG}
+
   when:
     event: tag
 
diff --git a/.editorconfig b/.editorconfig
index ab2168d..146a218 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -10,3 +10,7 @@ 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
new file mode 100644
index 0000000..1d953f4
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use nix
diff --git a/.gitignore b/.gitignore
index 47894a2..aae5f18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
 build/
 docs/
 
-tests/testing_latest/
-tests/testing_bak/
+tests/testing/
 
 include/sql/*.h
+
+emgauwa-core.conf.d
+
+emgauwa-core.sqlite
+vgcore.*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a763453..39a60be 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required (VERSION 3.7)
 project(core
-        VERSION 0.3.4
+        VERSION 0.4.5
         LANGUAGES C)
 
 add_executable(core src/main.c)
@@ -8,11 +8,18 @@ add_executable(core src/main.c)
 target_link_libraries(core -lsqlite3)
 target_link_libraries(core -luuid)
 
-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 "${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_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)
@@ -24,7 +31,6 @@ 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)
 
 
@@ -39,27 +45,8 @@ add_custom_target(sql
 )
 
 add_custom_target(run
-    COMMAND ./core start
+    COMMAND ${CMAKE_BINARY_DIR}/core
     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}
 )
 
@@ -68,13 +55,57 @@ add_custom_target(test
     DEPENDS core
     WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
 )
-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
+
+add_custom_target(docs
+    COMMAND doxygen
     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 a186f7e..0f2ab0b 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.17
+# Doxyfile 1.8.20
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -227,6 +227,14 @@ 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.
@@ -263,12 +271,6 @@ 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
@@ -310,13 +312,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,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
 # 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), 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.
+# 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.
 #
 # Note: For files without extension you can use no_extension as a placeholder.
 #
@@ -455,6 +457,19 @@ 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
 #---------------------------------------------------------------------------
@@ -559,7 +574,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) ands Mac users are advised to set this option to NO.
+# (including Cygwin) and Mac users are advised to set this option to NO.
 # The default value is: system dependent.
 
 CASE_SENSE_NAMES       = NO
@@ -829,7 +844,8 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = .
+INPUT                  = src/ \
+                         include/
 
 # 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
@@ -853,7 +869,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, *.f, *.for, *.tcl, *.vhd,
+# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
 # *.vhdl, *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          = *.c \
@@ -1378,7 +1394,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 master .chm file (NO).
+# (YES) or that it should be included in the main .chm file (NO).
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
@@ -1514,7 +1530,7 @@ DISABLE_INDEX          = NO
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-GENERATE_TREEVIEW      = NO
+GENERATE_TREEVIEW      = YES
 
 # 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.
@@ -1540,6 +1556,17 @@ 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
@@ -1595,7 +1622,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://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
+# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
@@ -1834,9 +1861,11 @@ LATEX_EXTRA_FILES      =
 
 PDF_HYPERLINKS         = YES
 
-# 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.
+# 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.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -2075,6 +2104,10 @@ DOCBOOK_PROGRAMLISTING = NO
 
 GENERATE_AUTOGEN_DEF   = NO
 
+#---------------------------------------------------------------------------
+# Configuration options related to Sqlite3 output
+#---------------------------------------------------------------------------
+
 #---------------------------------------------------------------------------
 # Configuration options related to the Perl module output
 #---------------------------------------------------------------------------
@@ -2248,7 +2281,7 @@ EXTERNAL_PAGES         = YES
 # powerful graphs.
 # The default value is: YES.
 
-CLASS_DIAGRAMS         = YES
+CLASS_DIAGRAMS         = NO
 
 # 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
@@ -2270,7 +2303,7 @@ HIDE_UNDOC_RELATIONS   = YES
 # set to NO
 # The default value is: NO.
 
-HAVE_DOT               = NO
+HAVE_DOT               = YES
 
 # 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
@@ -2387,7 +2420,7 @@ INCLUDED_BY_GRAPH      = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALL_GRAPH             = NO
+CALL_GRAPH             = YES
 
 # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
 # dependency graph for every global function or class method.
@@ -2399,7 +2432,7 @@ CALL_GRAPH             = NO
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALLER_GRAPH           = NO
+CALLER_GRAPH           = YES
 
 # 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
deleted file mode 100644
index cd40564..0000000
--- a/core.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-[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
new file mode 100644
index 0000000..d799d82
--- /dev/null
+++ b/emgauwa-core.conf
@@ -0,0 +1,19 @@
+[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 8fde368..3e2ff7c 100644
--- a/include/cache.h
+++ b/include/cache.h
@@ -36,7 +36,6 @@ void
 cache_invalidate_relay(int relay_id, int status_relay);
 
 
-
 void
 cache_put_json_controller(int controller_id, char *controller_json);
 
@@ -47,6 +46,16 @@ 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
new file mode 100644
index 0000000..cf3e2b1
--- /dev/null
+++ b/include/cli.h
@@ -0,0 +1,12 @@
+#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 49c04af..ca9fbff 100644
--- a/include/config.h
+++ b/include/config.h
@@ -4,37 +4,54 @@
 #include <stdint.h>
 
 #include <mongoose.h>
-#include <confini.h>
-
-typedef enum
-{
-    RUN_TYPE_START,
-    RUN_TYPE_INVALID,
-} run_type_t;
+#include <toml.h>
 
 typedef struct
 {
-    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];
+    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;
+
     struct mg_serve_http_opts http_server_opts;
 } config_t;
 
-extern config_t global_config;
+extern config_t *global_config;
 
-int
-config_load(IniDispatch *disp, void *config_void);
+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);
 
 #endif /* CORE_CONFIG_H */
diff --git a/include/constants.h b/include/constants.h
index 329868c..d92ad15 100644
--- a/include/constants.h
+++ b/include/constants.h
@@ -2,6 +2,7 @@
 #define CORE_CONTANTS_H
 
 #define SECONDS_PER_DAY 86400 // 60 * 60 * 24
+#define DAYS_PER_WEEK 7
 
 #define SECONDS_PER_MINUTE 60
 
@@ -14,13 +15,6 @@
  */
 #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
  */
@@ -28,4 +22,11 @@
 
 #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 dde8177..a97879c 100644
--- a/include/database.h
+++ b/include/database.h
@@ -3,6 +3,8 @@
 
 #include <sqlite3.h>
 
+typedef int database_transaction_lock;
+
 extern sqlite3 *global_database;
 
 void
@@ -15,14 +17,14 @@ void
 database_migrate();
 
 
-int
-database_transaction_begin();
+void
+database_transaction_begin(database_transaction_lock *lock);
 
 void
-database_transaction_commit();
+database_transaction_commit(const database_transaction_lock *lock);
 
 void
-database_transaction_rollback();
+database_transaction_rollback(const database_transaction_lock *lock);
 
 
 int
diff --git a/include/endpoint.h b/include/endpoint.h
index 6447487..c259938 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;
-    const char *content;
+    char *content;
     int alloced_content;
 } endpoint_response_t;
 
@@ -53,10 +53,16 @@ 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 6430079..5d96497 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_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
+api_v1_controllers_discover_PUT(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
new file mode 100644
index 0000000..9a79271
--- /dev/null
+++ b/include/endpoints/api_v1_macros.h
@@ -0,0 +1,24 @@
+#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 e2ead83..8b322a7 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 fc207f5..ba95065 100644
--- a/include/logger.h
+++ b/include/logger.h
@@ -8,15 +8,22 @@
 #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_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__)
+#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__)
 
 #endif //EMGAUWA_LOGGER_H
diff --git a/include/macros.h b/include/macros.h
index 4242abe..1781aad 100644
--- a/include/macros.h
+++ b/include/macros.h
@@ -1,6 +1,30 @@
 #ifndef CORE_MACROS_H
 #define CORE_MACROS_H
 
-#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
+#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")
 
 #endif /* CORE_MACROS_H */
diff --git a/include/models/controller.h b/include/models/controller.h
index e144a0d..f12f96a 100644
--- a/include/models/controller.h
+++ b/include/models/controller.h
@@ -25,16 +25,16 @@ typedef struct
 } controller_t;
 
 void
-controller_free(controller_t* contoller);
+controller_free(controller_t *controller);
 
 int
-controller_save(controller_t* contoller);
+controller_save(controller_t *controller);
 
 int
-controller_remove(controller_t* contoller);
+controller_remove(controller_t *controller);
 
 cJSON*
-controller_to_json(controller_t* contoller);
+controller_to_json(controller_t *controller);
 
 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 d8ef168..e3c81ac 100644
--- a/include/models/junction_relay_schedule.h
+++ b/include/models/junction_relay_schedule.h
@@ -10,4 +10,7 @@ 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
new file mode 100644
index 0000000..29b3e70
--- /dev/null
+++ b/include/models/macro.h
@@ -0,0 +1,45 @@
+#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
new file mode 100644
index 0000000..a46ed19
--- /dev/null
+++ b/include/models/macro_action.h
@@ -0,0 +1,36 @@
+#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 fcec754..c879eec 100644
--- a/include/models/schedule.h
+++ b/include/models/schedule.h
@@ -35,9 +35,6 @@ 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);
 
@@ -65,4 +62,10 @@ 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
new file mode 100644
index 0000000..dac87db
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,8 @@
+with import <nixpkgs> {};
+mkShell {
+	nativeBuildInputs = [
+        cmake
+        sqlite
+        util-linux
+	];
+}
diff --git a/sql/cache.sql b/sql/cache.sql
index 5ca7471..3e4ce21 100644
--- a/sql/cache.sql
+++ b/sql/cache.sql
@@ -1,3 +1,5 @@
+-- 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 12dda72..ec39fba 100644
--- a/sql/migration_0.sql
+++ b/sql/migration_0.sql
@@ -1,4 +1,6 @@
-create table controllers
+-- base migration
+
+CREATE TABLE controllers
 (
     id      INTEGER
             PRIMARY KEY
@@ -14,7 +16,7 @@ create table controllers
                 NOT NULL
 );
 
-create table relays
+CREATE TABLE relays
 (
     id              INTEGER
                     PRIMARY KEY
@@ -28,7 +30,7 @@ create table relays
                     ON DELETE CASCADE
 );
 
-create table schedules
+CREATE TABLE schedules
 (
     id      INTEGER
             PRIMARY KEY
@@ -40,7 +42,7 @@ create table schedules
     periods BLOB
 );
 
-create table tags
+CREATE TABLE tags
 (
     id      INTEGER
             PRIMARY KEY
@@ -50,7 +52,7 @@ create table tags
             UNIQUE
 );
 
-create table junction_tag
+CREATE TABLE junction_tag
 (
     tag_id          INTEGER
                     NOT NULL
@@ -64,7 +66,7 @@ create table junction_tag
                     ON DELETE CASCADE
 );
 
-create table junction_relay_schedule
+CREATE TABLE junction_relay_schedule
 (
     weekday         SMALLINT
                     NOT NULL,
@@ -78,4 +80,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'010000009F05');
+INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000',  'on', x'010000000000');
diff --git a/sql/migration_1.sql b/sql/migration_1.sql
new file mode 100644
index 0000000..5078763
--- /dev/null
+++ b/sql/migration_1.sql
@@ -0,0 +1,28 @@
+-- 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 2636ead..ce70088 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1,7 +1,9 @@
 #include <cache.h>
 #include <logger.h>
-#include <sql/cache.h>
+#include <models/junction_relay_schedule.h>
 #include <models/junction_tag.h>
+#include <models/macro_action.h>
+#include <sql/cache.h>
 
 
 sqlite3 *cache_database;
@@ -23,8 +25,15 @@ cache_get_value(char *key)
         if (s == SQLITE_ROW)
         {
             const char *found_value = (const char *)sqlite3_column_text(stmt, 0);
-            result = (char*)malloc(sizeof(char) * (strlen(found_value) + 1));
-            strcpy(result, found_value);
+            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';
         }
         else
         {
@@ -32,11 +41,9 @@ cache_get_value(char *key)
             {
                 break;
             }
-            else
-            {
-                LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s));
-                break;
-            }
+
+            LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s));
+            break;
         }
     }
 
@@ -134,14 +141,19 @@ cache_invalidate_schedule(int schedule_id)
     sprintf(key, "schedule_json:%d", schedule_id);
     cache_invalidate(key);
 
-    relay_t **relays = relay_get_with_schedule(schedule_id);
-
-    for(int i = 0; relays[i] != NULL; ++i)
+    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(relays[i]->id, -1);
+        cache_invalidate_relay(relay_ids[i], -1);
     }
+    free(relay_ids);
 
-    relay_free_list(relays);
+    int *macro_ids = macro_action_get_macro_ids_with_schedule(schedule_id);
+    for(int i = 0; macro_ids[i] != 0; ++i)
+    {
+        cache_invalidate_macro(macro_ids[i]);
+    }
+    free(macro_ids);
 }
 
 
@@ -180,6 +192,13 @@ 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);
 }
 
 
@@ -208,6 +227,34 @@ 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/helpers/parse_cli.c b/src/cli.c
similarity index 58%
rename from src/helpers/parse_cli.c
rename to src/cli.c
index f205541..2719aa1 100644
--- a/src/helpers/parse_cli.c
+++ b/src/cli.c
@@ -5,25 +5,29 @@
 #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
-helper_parse_cli(int argc, const char **argv, config_t *config)
+cli_parse(int argc, const char **argv, cli_t *cli)
 {
+    cli->config_file = NULL;
+
     int version = 0;
     struct argparse_option options[] =
     {
         OPT_HELP(),
         OPT_GROUP("Basic options"),
-        OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG),
+        OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG),
         OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG),
         OPT_END(),
     };
@@ -35,29 +39,11 @@ helper_parse_cli(int argc, const char **argv, config_t *config)
             "\nA brief description of what the program does and how it works.",
             "\nAdditional description of the program after the description of the arguments."
     );
-    argc = argparse_parse(&argparse, argc, argv);
+    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/command.c b/src/command.c
index ec299ad..8fb0ac4 100644
--- a/src/command.c
+++ b/src/command.c
@@ -90,13 +90,17 @@ 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;
 }
 
@@ -207,8 +211,6 @@ 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)
@@ -217,12 +219,12 @@ command_send(controller_t *controller, char *payload, uint32_t payload_size)
         return 1;
     }
 
-    if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0)
+    if(send(fd_controller, &payload_size, sizeof(payload_size), 0) <= 0)
     {
         LOGGER_ERR("error during sending size\n");
         return 1;
     }
-    if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0)
+    if(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 a674c86..8eaebdf 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,151 +1,380 @@
+#include <dirent.h>
+#include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include <logger.h>
 #include <config.h>
+#include <constants.h>
+#include <logger.h>
 
-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))
+config_t *global_config;
 
 static int
-config_load_log_level(IniDispatch *disp, config_t *config)
+config_load_log_level(config_t *config, char *value)
 {
-    if(strcasecmp(disp->value, "debug") == 0)
+    if(strcmp(value, "debug") == 0)
     {
         setlogmask(LOG_UPTO(LOG_DEBUG));
-        config->log_level = LOG_DEBUG;
+        config->logging.level = LOG_DEBUG;
         return 0;
     }
-    if(strcasecmp(disp->value, "info") == 0)
+    if(strcmp(value, "info") == 0)
     {
         setlogmask(LOG_UPTO(LOG_INFO));
-        config->log_level = LOG_INFO;
+        config->logging.level = LOG_INFO;
         return 0;
     }
-    if(strcasecmp(disp->value, "notice") == 0)
+    if(strcmp(value, "notice") == 0)
     {
         setlogmask(LOG_UPTO(LOG_NOTICE));
-        config->log_level = LOG_NOTICE;
+        config->logging.level = LOG_NOTICE;
         return 0;
     }
-    if(strcasecmp(disp->value, "warning") == 0)
+    if(strcmp(value, "warning") == 0)
     {
         setlogmask(LOG_UPTO(LOG_WARNING));
-        config->log_level = LOG_WARNING;
+        config->logging.level = LOG_WARNING;
         return 0;
     }
-    if(strcasecmp(disp->value, "err") == 0)
+    if(strcmp(value, "err") == 0)
     {
         setlogmask(LOG_UPTO(LOG_ERR));
-        config->log_level = LOG_ERR;
+        config->logging.level = LOG_ERR;
         return 0;
     }
-    if(strcasecmp(disp->value, "crit") == 0)
+    if(strcmp(value, "crit") == 0)
     {
         setlogmask(LOG_UPTO(LOG_CRIT));
-        config->log_level = LOG_CRIT;
+        config->logging.level = LOG_CRIT;
         return 0;
     }
-    if(strcasecmp(disp->value, "emerg") == 0)
+    if(strcmp(value, "emerg") == 0)
     {
         setlogmask(LOG_UPTO(LOG_EMERG));
-        config->log_level = LOG_EMERG;
+        config->logging.level = LOG_EMERG;
         return 0;
     }
-    LOGGER_WARNING("invalid log-level '%s'\n", disp->value);
+    LOGGER_WARNING("invalid log-level '%s'\n", value);
+
     return 0;
 }
 
 static int
-config_load_log_file(IniDispatch *disp, config_t *config)
+config_load_log_file(config_t *config, char *value)
 {
-    if(strcasecmp(disp->value, "stdout") == 0)
+    if(strcmp(value, "stdout") == 0)
     {
-        config->log_file = stdout;
+        config->logging.file = stdout;
         return 0;
     }
-    if(strcasecmp(disp->value, "stderr") == 0)
+    if(strcmp(value, "stderr") == 0)
     {
-        config->log_file = stderr;
+        config->logging.file = stderr;
         return 0;
     }
-    config->log_file = fopen(disp->value, "a+");
+    config->logging.file = fopen(value, "a+");
     return 0;
 }
 
-int
-config_load(IniDispatch *disp, void *config_void)
+static void
+config_load_section_core(config_t *config, toml_table_t* core)
 {
-    config_t *config = (config_t*)config_void;
+    toml_datum_t config_entry;
 
-    if(disp->type == INI_KEY)
+    config_entry = toml_string_in(core, "database");
+    if(config_entry.ok)
     {
-        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;
-        }
+        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; 
     }
     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 02fd75a..05474f8 100644
--- a/src/database.c
+++ b/src/database.c
@@ -5,14 +5,15 @@
 #include <database.h>
 
 #include <sql/migration_0.h>
+#include <sql/migration_1.h>
 
 sqlite3 *global_database;
-static int in_transaction;
+static database_transaction_lock *transaction_lock;
 
 void
 database_init()
 {
-    int rc = sqlite3_open(global_config.database, &global_database);
+    int rc = sqlite3_open(global_config->database, &global_database);
 
     if(rc)
     {
@@ -20,10 +21,12 @@ 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);
-    in_transaction = 0;
+    transaction_lock = NULL;
 }
 
 void
@@ -32,11 +35,35 @@ 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, rc;
+    int s;
     sqlite3_stmt *stmt;
     sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
     s = sqlite3_step(stmt);
@@ -49,61 +76,52 @@ database_migrate()
         version_num = 0;
     }
 
-    uint16_t new_version_num = version_num;
-    char* err_msg;
-
     sqlite3_finalize(stmt);
 
     switch(version_num)
     {
         case 0:
-            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;
+            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));
         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;
 }
 
-int
-database_transaction_begin()
+void
+database_transaction_begin(database_transaction_lock *lock)
 {
-    if(!in_transaction)
+    if(transaction_lock == NULL)
     {
         LOGGER_DEBUG("beginning transaction\n");
         sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
-        in_transaction = 1;
-        return 1;
+        transaction_lock = lock;
     }
-    return 0;
 }
 
 void
-database_transaction_commit()
+database_transaction_commit(const database_transaction_lock *lock)
 {
-    LOGGER_DEBUG("commiting transaction\n");
-    sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
-    in_transaction = 0;
+    if(transaction_lock == lock)
+    {
+        LOGGER_DEBUG("commiting transaction\n");
+        sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
+        transaction_lock = NULL;
+    }
 }
 
 void
-database_transaction_rollback()
+database_transaction_rollback(const database_transaction_lock *lock)
 {
-    LOGGER_DEBUG("rolling back transaction\n");
-    sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
-    in_transaction = 0;
+    if(transaction_lock == lock)
+    {
+        LOGGER_DEBUG("rolling back transaction\n");
+        sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
+        transaction_lock = NULL;
+    }
 }
 
 int
@@ -126,12 +144,10 @@ database_helper_get_id(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
-            {
-                LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
-                sqlite3_finalize(stmt);
-                return 0;
-            }
+
+            LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
+            sqlite3_finalize(stmt);
+            return 0;
         }
     }
 
@@ -170,12 +186,14 @@ database_helper_get_ids(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
+
+            if(result)
             {
-                LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
-                sqlite3_finalize(stmt);
-                return NULL;
+                free(result);
             }
+            LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
+            sqlite3_finalize(stmt);
+            return NULL;
         }
     }
 
@@ -198,8 +216,15 @@ database_helper_get_string(sqlite3_stmt *stmt)
         if (s == SQLITE_ROW)
         {
             const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
-            result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
-            strcpy(result, found_string);
+            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';
         }
         else
         {
@@ -207,12 +232,14 @@ database_helper_get_string(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
+
+            if(result)
             {
-                LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
-                sqlite3_finalize(stmt);
-                return NULL;
+                free(result);
             }
+            LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
+            sqlite3_finalize(stmt);
+            return NULL;
         }
     }
 
@@ -236,12 +263,13 @@ database_helper_get_strings(sqlite3_stmt *stmt)
         if (s == SQLITE_ROW)
         {
             const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
-            int new_string_len = strlen(new_string);
+            size_t new_string_len = sqlite3_column_bytes(stmt, 0);
             row++;
 
             result = (char**)realloc(result, sizeof(char*) * (row + 1));
             result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
-            strcpy(result[row - 1], new_string);
+            strncpy(result[row - 1], new_string, new_string_len);
+            result[row - 1][new_string_len] = '\0';
         }
         else
         {
@@ -249,11 +277,9 @@ database_helper_get_strings(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
-            {
-                LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
-                break;
-            }
+
+            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 a56f277..d56838f 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,27 +24,61 @@ 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 = "";
@@ -53,22 +87,27 @@ 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 >= 0)
+    if(content_length)
     {
         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);
@@ -84,8 +123,16 @@ endpoint_response_json(endpoint_response_t *response, int status_code, const cJS
         }
     }
 
-    LOGGER_ERR("failed to print schedule json\n");
-
-    static const char content[] = "failed to print json";
-    endpoint_response_text(response, status_code, content, STRLEN(content));
+    M_RESPONSE_MSG(LOGGER_ERR, response, 500, "failed to print json");
+}
+
+void
+endpoint_response_free_content(endpoint_response_t *response)
+{
+    if(response->alloced_content)
+    {
+        free(response->content);
+        response->content = NULL;
+        response->alloced_content = false;
+    }
 }
diff --git a/src/endpoints/api_v1_controllers_STR.c b/src/endpoints/api_v1_controllers_STR.c
index eb4c24d..5d5c370 100644
--- a/src/endpoints/api_v1_controllers_STR.c
+++ b/src/endpoints/api_v1_controllers_STR.c
@@ -18,10 +18,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -29,10 +26,7 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
     LOGGER_DEBUG("returning controller for uid '%s'\n", args[0].value.v_str);
@@ -53,10 +47,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -64,10 +55,7 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
     LOGGER_DEBUG("starting overwrite for controller %s\n", args[0].value.v_str);
@@ -76,70 +64,64 @@ 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)
+        if(!cJSON_IsString(json_name) || json_name->valuestring == NULL)
         {
-            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)
+        if(!cJSON_IsString(json_ip) || json_ip->valuestring == NULL)
         {
-            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;
-            }
+            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);
         }
         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);
 
-        static const char content[] = "failed to save controller to database";
-        endpoint_response_text(response, 500, content, STRLEN(content));
+        M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
         return;
     }
     LOGGER_DEBUG("saved controller %s\n", args[0].value.v_str);
@@ -165,10 +147,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -176,25 +155,17 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm,
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
 
     if(controller_remove(controller))
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
     }
     else
     {
-        LOGGER_DEBUG("deleted controller %s\n", args[0].value.v_str);
-        endpoint_response_text(response, 200, "", 0);
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted controller");
     }
     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 6accc90..ad8a83e 100644
--- a/src/endpoints/api_v1_controllers_STR_relays.c
+++ b/src/endpoints/api_v1_controllers_STR_relays.c
@@ -15,10 +15,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -26,10 +23,7 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
         
diff --git a/src/endpoints/api_v1_controllers_STR_relays_INT.c b/src/endpoints/api_v1_controllers_STR_relays_INT.c
index 738ad39..d470ef2 100644
--- a/src/endpoints/api_v1_controllers_STR_relays_INT.c
+++ b/src/endpoints/api_v1_controllers_STR_relays_INT.c
@@ -18,10 +18,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -29,21 +26,18 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
 
-    relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
+    int controller_id = controller->id;
+    controller_free(controller);
+
+    relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
 
     if(!relay)
     {
-        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));
+        M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
         return;
     }
 
@@ -53,7 +47,6 @@ 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
@@ -65,10 +58,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -76,23 +66,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
 
-    if(args[1].value.v_int > controller->relay_count)
+    int controller_id = controller->id;
+    int controller_relay_count = controller->relay_count;
+    controller_free(controller);
+
+    if(args[1].value.v_int > controller_relay_count)
     {
-        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));
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "relay number is too high for this controller");
         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)
     {
@@ -102,7 +90,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));
@@ -117,15 +105,13 @@ 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)
     {
-        static const char content[] = "no valid json was supplied";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_JSON(response);
         return;
     }
 
@@ -149,21 +135,17 @@ 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);
 
-                static const char content[] = "at least one schedule is missing an id";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one schedule is missing an id");
                 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);
 
-                static const char content[] = "at least one schedule has a bad id";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "at least one schedule has a bad id");
                 return;
             }
 
@@ -190,11 +172,9 @@ 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);
 
-                static const char content[] = "active_schedule has a bad id";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "active schedule has a bad uid");
                 return;
             }
             relay->schedules[day_of_week] = schedule_get_by_uid_or_off(target_uid);
@@ -202,21 +182,15 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
         }
     }
 
-    int opened_transaction = database_transaction_begin();
+    database_transaction_lock lock;
+    database_transaction_begin(&lock);
      
     if(relay_save(relay))
     {
-        LOGGER_ERR("failed to save relay\n");
-
-        if(opened_transaction)
-        {
-            database_transaction_rollback();
-        }
-
+        database_transaction_rollback(&lock);
         cJSON_Delete(json);
 
-        static const char content[] = "failed to save relay to database";
-        endpoint_response_text(response, 500, content, STRLEN(content));
+        M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
         return;
     }
 
@@ -235,19 +209,12 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
         {
             if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
             {
-                LOGGER_DEBUG("invalid tag in tags\n");
-
-                if(opened_transaction)
-                {
-                    database_transaction_rollback();
-                }
-
+                database_transaction_rollback(&lock);
                 relay_free(relay);
                 cJSON_Delete(json);
                 free(tag_ids);
 
-                static const char content[] = "invalid tag in tags";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "invalid tag in tags");
                 return;
             }
             const char *tag = json_tag->valuestring;
@@ -259,13 +226,12 @@ 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);
     }
 
-    if(opened_transaction)
-    {
-        database_transaction_commit();
-    }
+    database_transaction_commit(&lock);
 
     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 22b158a..9398b64 100644
--- a/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c
+++ b/src/endpoints/api_v1_controllers_STR_relays_INT_pulse.c
@@ -18,10 +18,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -29,21 +26,18 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
 
     if(!controller)
     {
-        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));
+        M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
         return;
     }
 
-    relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
+    int controller_id = controller->id;
+    controller_free(controller);
+
+    relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
 
     if(!relay)
     {
-        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));
+        M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
         return;
     }
 
@@ -64,7 +58,6 @@ 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);
 
-    endpoint_response_text(response, 200, "", 0);
+    M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "sent pulse");
     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 d2e00d7..00e7042 100644
--- a/src/endpoints/api_v1_controllers_discover.c
+++ b/src/endpoints/api_v1_controllers_discover.c
@@ -19,7 +19,8 @@ typedef enum
 static int
 bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
 {
-    struct addrinfo hints, *res;
+    struct addrinfo hints;
+    struct addrinfo *res;
     int fd;
     int status;
 
@@ -105,7 +106,7 @@ send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length
 }
 
 void
-api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
+api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
 {
     (void)nc;
     (void)hm;
@@ -114,13 +115,7 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
 
     if(discover_server_port == -1)
     {
-        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;
+        M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to prepare discovery");
         return;
     }
 
@@ -128,21 +123,16 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
     payload[0] = discover_server_port;
 
     LOGGER_DEBUG("sending udp broadcast\n");
-    if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0)
+    if(send_udp_broadcast("255.255.255.255", global_config->ports.discovery, payload, sizeof(payload)) < 0)
     {
-        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;
+        M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to send discovery broadcast");
         return;
     }
 
     struct sockaddr_storage their_addr;
     socklen_t addr_size;
-    int client_fd, s_ret;
+    int s_ret;
+    int client_fd;
     fd_set accept_fds;
     struct timeval timeout;
 
@@ -165,145 +155,147 @@ api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *
         {
             break;
         }
-        else
+
+        if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
         {
-            if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 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 + 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)
             {
-                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)
+                if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0)
                 {
-                    if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0)
-                    {
-                        LOGGER_DEBUG("rediscovered a known controller at %s\n", inet_ntoa(addr.sin_addr));
+                    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);
-                        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;
+                    known_controllers[i]->active = 1;
+                    strncpy(known_controllers[i]->name, discovered_name, discovered_name_len);
+                    known_controllers[i]->name[discovered_name_len] = '\0';
 
-                        controller_save(known_controllers[i]);
-                        controller_free(known_controllers[i]);
+                    strncpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr), IP_LENGTH);
+                    known_controllers[i]->ip[IP_LENGTH] = '\0';
 
-                        found_discovered_in_list = 1;
-                        known_controllers[i] = known_controllers[i + 1];
-                    }
-                }
-                else
-                {
+                    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;
                     known_controllers[i] = known_controllers[i + 1];
                 }
             }
-
-            if(!found_discovered_in_list)
+            else
             {
-                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_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)
-                    {
-                        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;
-                }
-                discovered_relays[discovered_controller->relay_count] = NULL;
-                discovered_controller->relays = discovered_relays;
-
-                controller_free(discovered_controller);
+                known_controllers[i] = known_controllers[i + 1];
             }
-            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);
         }
+
+        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;
+
+            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)
+                {
+                    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;
+            }
+            discovered_relays[discovered_controller->relay_count] = NULL;
+            discovered_controller->relays = discovered_relays;
+
+            controller_free(discovered_controller);
+        }
+        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
new file mode 100644
index 0000000..e9cc5ac
--- /dev/null
+++ b/src/endpoints/api_v1_macros.c
@@ -0,0 +1,240 @@
+#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
new file mode 100644
index 0000000..5c4c5fb
--- /dev/null
+++ b/src/endpoints/api_v1_macros_STR.c
@@ -0,0 +1,280 @@
+#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
new file mode 100644
index 0000000..5097d85
--- /dev/null
+++ b/src/endpoints/api_v1_macros_STR_execute.c
@@ -0,0 +1,85 @@
+#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 06a6adc..ffce429 100644
--- a/src/endpoints/api_v1_relays_tag_STR.c
+++ b/src/endpoints/api_v1_relays_tag_STR.c
@@ -16,17 +16,13 @@ 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)
     {
-        static const char content[] = "tag was not found";
-        endpoint_response_text(response, 404, content, STRLEN(content));
+        M_RESPONSE_404_NO_TAG_FOUND(response);
         return;
     }
     int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
     if(relays_ids == NULL)
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
         return;
     }
 
diff --git a/src/endpoints/api_v1_schedules.c b/src/endpoints/api_v1_schedules.c
index 2dad24d..0b1599d 100644
--- a/src/endpoints/api_v1_schedules.c
+++ b/src/endpoints/api_v1_schedules.c
@@ -16,29 +16,25 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
 
     if(json == NULL)
     {
-        static const char content[] = "no valid json was supplied";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_JSON(response);
         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);
 
-        static const char content[] = "no name for schedule provided";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_NAME(response);
         return;
     }
+
     cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
     if(!cJSON_IsArray(json_periods))
     {
-        LOGGER_DEBUG("no periods for schedule provided\n");
         cJSON_Delete(json);
 
-        static const char content[] = "no periods for schedule provided";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods");
         return;
     }
 
@@ -48,11 +44,9 @@ 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);
 
-            static const char content[] = "invalid tag in tags";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
             return;
         }
     }
@@ -78,22 +72,18 @@ 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);
 
-            static const char content[] = "one period is missing a start";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
             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);
 
-            static const char content[] = "one period is missing an end";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
             return;
         }
 
@@ -101,22 +91,18 @@ 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);
 
-            static const char content[] = "the start for one period is invalid";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
             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);
 
-            static const char content[] = "the end for one period is invalid";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
             return;
         }
 
diff --git a/src/endpoints/api_v1_schedules_STR.c b/src/endpoints/api_v1_schedules_STR.c
index 03613d1..5397e49 100644
--- a/src/endpoints/api_v1_schedules_STR.c
+++ b/src/endpoints/api_v1_schedules_STR.c
@@ -19,10 +19,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -30,10 +27,7 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp
 
     if(!schedule)
     {
-        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));
+        M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
         return;
     }
 
@@ -53,10 +47,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -64,10 +55,7 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
 
     if(!schedule)
     {
-        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));
+        M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
         return;
     }
 
@@ -75,8 +63,7 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
 
     if(json == NULL)
     {
-        static const char content[] = "no valid json was supplied";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_JSON(response);
         return;
     }
 
@@ -137,12 +124,10 @@ 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);
 
-        static const char content[] = "failed to save schedule to database";
-        endpoint_response_text(response, 500, content, STRLEN(content));
+        M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
         return;
     }
 
@@ -190,10 +175,7 @@ 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))
     {
-        LOGGER_DEBUG("failed to unparse uid\n");
-
-        static const char content[] = "given id was invalid";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_ID(response);
         return;
     }
 
@@ -201,33 +183,25 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e
 
     if(!schedule)
     {
-        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));
+        M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
         return;
     }
 
     if(schedule_is_protected(schedule))
     {
-        static const char content[] = "target schedule is protected";
-        endpoint_response_text(response, 403, content, STRLEN(content));
-
         schedule_free(schedule);
+
+        M_RESPONSE_403_PROTECTED_SCHEDULE(response);
         return;
     }
 
     if(schedule_remove(schedule))
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
     }
     else
     {
-        endpoint_response_text(response, 200, "", 0);
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the target schedule got deleted");
     }
     schedule_free(schedule);
-    return;
 }
diff --git a/src/endpoints/api_v1_schedules_list.c b/src/endpoints/api_v1_schedules_list.c
index ede980b..d9be32a 100644
--- a/src/endpoints/api_v1_schedules_list.c
+++ b/src/endpoints/api_v1_schedules_list.c
@@ -20,29 +20,23 @@ api_v1_schedules_list_POST(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));
+            M_RESPONSE_400_NO_VALID_JSON(response);
             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_list);
-
-            static const char content[] = "no name for schedule provided";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name for at least one schedule");
             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);
 
-            static const char content[] = "no periods for schedule provided";
-            endpoint_response_text(response, 400, content, STRLEN(content));
+            M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods for at least one schedule");
             return;
         }
 
@@ -52,11 +46,9 @@ 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);
 
-                static const char content[] = "invalid tag in tags";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
                 return;
             }
         }
@@ -82,22 +74,18 @@ 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);
 
-                static const char content[] = "one period is missing a start";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
                 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);
 
-                static const char content[] = "one period is missing an end";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
                 return;
             }
 
@@ -105,22 +93,18 @@ 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);
 
-                static const char content[] = "the start for one period is invalid";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
                 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);
 
-                static const char content[] = "the end for one period is invalid";
-                endpoint_response_text(response, 400, content, STRLEN(content));
+                M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
                 return;
             }
 
diff --git a/src/endpoints/api_v1_schedules_tag_STR.c b/src/endpoints/api_v1_schedules_tag_STR.c
index b0d22ec..08723dc 100644
--- a/src/endpoints/api_v1_schedules_tag_STR.c
+++ b/src/endpoints/api_v1_schedules_tag_STR.c
@@ -16,17 +16,13 @@ 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)
     {
-        static const char content[] = "tag was not found";
-        endpoint_response_text(response, 404, content, STRLEN(content));
+        M_RESPONSE_404_NO_TAG_FOUND(response);
         return;
     }
     int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
     if(schedules_ids == NULL)
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
         return;
     }
 
diff --git a/src/endpoints/api_v1_tags.c b/src/endpoints/api_v1_tags.c
index 2bd4afa..5ab74c5 100644
--- a/src/endpoints/api_v1_tags.c
+++ b/src/endpoints/api_v1_tags.c
@@ -44,56 +44,54 @@ api_v1_tags_POST(struct mg_connection *nc, struct http_message *hm, endpoint_arg
 
     if(json == NULL)
     {
-        static const char content[] = "no valid json was supplied";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_400_NO_VALID_JSON(response);
         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);
 
-        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);
-
-        static const char content[] = "tag existed already";
-        endpoint_response_text(response, 400, content, STRLEN(content));
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a tag");
         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));
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an empty tag");
+        return;
+    }
+
+    if(tag_get_id(json_tag->valuestring))
+    {
+        cJSON_Delete(json);
+
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the tag does already exist");
         return;
     }
 
     if(tag_save(0, json_tag->valuestring))
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
     }
     else
     {
         LOGGER_DEBUG("new tag saved\n");
 
-        endpoint_response_text(response, 201, json_tag->valuestring, 0);
+        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);
     }
 
     cJSON_Delete(json);
-    return;
 }
diff --git a/src/endpoints/api_v1_tags_STR.c b/src/endpoints/api_v1_tags_STR.c
index c9f0426..6bf0d9c 100644
--- a/src/endpoints/api_v1_tags_STR.c
+++ b/src/endpoints/api_v1_tags_STR.c
@@ -16,26 +16,19 @@ 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)
     {
-        static const char content[] = "tag was not found";
-        endpoint_response_text(response, 404, content, STRLEN(content));
+        M_RESPONSE_404_NO_TAG_FOUND(response);
         return;
     }
     int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
     if(relays_ids == NULL)
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
         return;
     }
     int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
     if(schedules_ids == NULL)
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
         return;
     }
 
@@ -91,20 +84,16 @@ 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)
     {
-        static const char content[] = "tag was not found";
-        endpoint_response_text(response, 404, content, STRLEN(content));
+        M_RESPONSE_404_NO_TAG_FOUND(response);
         return;
     }
 
     if(tag_remove(tag_id))
     {
-        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));
+        M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
     }
     else
     {
-        endpoint_response_text(response, 200, "", 0);
+        M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the tag got deleted");
     }
 }
diff --git a/src/handlers/http.c b/src/handlers/http.c
index 87d2908..b72db52 100644
--- a/src/handlers/http.c
+++ b/src/handlers/http.c
@@ -17,16 +17,17 @@ 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);
+        strcpy(result, global_config->http_server_opts.extra_headers);
+        result[std_headers_len] = '\0';
         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;
 }
 
@@ -44,11 +45,6 @@ send_response(struct mg_connection *nc, endpoint_response_t *response)
 
         free(response_headers);
         free(extra_headers);
-
-        if(response->alloced_content)
-        {
-            free((char*)response->content);
-        }
     }
 }
 
@@ -82,15 +78,18 @@ 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;
-    static const char content[] = "the server did not create a response";
-    endpoint_response_text(&response, 500, content, STRLEN(content));
+    response.content = NULL;
+    response.alloced_content = false;
+
+    M_RESPONSE_MSG(LOGGER_NONE, &response, 500, "server did not create a response");
 
     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");
@@ -105,23 +104,22 @@ 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;
         }
-        else
-        {
-            LOGGER_DEBUG("serving 'not found'\n");
-            endpoint = router_get_not_found_endpoint();
-        }
+
+        LOGGER_DEBUG("serving 'not found'\n");
+        endpoint = router_get_not_found_endpoint();
     }
 
     if(endpoint->method == HTTP_METHOD_OPTIONS)
@@ -148,6 +146,7 @@ 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 7f26ffc..886e556 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, char *payload)
+handle_mqtt_publish_controller(char **topic_save, int controller_id, const char *payload)
 {
     (void)controller_id;
     (void)payload;
diff --git a/src/helpers/connect_server.c b/src/helpers/connect_server.c
index dbfdc80..7f01a74 100644
--- a/src/helpers/connect_server.c
+++ b/src/helpers/connect_server.c
@@ -13,8 +13,11 @@ helper_connect_tcp_server(char* host, uint16_t port)
     char port_str[6];
     sprintf(port_str, "%d", port);
 
-    int s, status;
-    struct addrinfo hints, *res;
+    int s;
+    int status;
+    struct addrinfo *res;
+    struct addrinfo hints;
+
     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
@@ -27,7 +30,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 ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
+    if (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 e2dde8a..ff95f63 100644
--- a/src/helpers/drop_privileges.c
+++ b/src/helpers/drop_privileges.c
@@ -15,21 +15,20 @@ get_uid_for_user(char *user)
     {
         return getuid();
     }
-    struct passwd *pwd = calloc(1, sizeof(struct passwd));
+    struct passwd pwd;
+    struct passwd *result = NULL;
     size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
     char *buffer = malloc(buffer_len);
-    getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
+    getpwnam_r(user, &pwd, buffer, buffer_len, &result);
 
-    if(pwd == NULL)
+    if(result == NULL)
     {
         LOGGER_CRIT("couldn't find user to drop privileges\n");
         exit(1);
     }
 
-    uid_t result = pwd->pw_uid;
     free(buffer);
-    free(pwd);
-    return result;
+    return result->pw_uid;
 }
 
 static gid_t
@@ -39,28 +38,27 @@ get_gid_for_group(char *group)
     {
         return getgid();
     }
-    struct group *grp = calloc(1, sizeof(struct group));
+    struct group grp;
+    struct group *result = NULL;
     size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
     char *buffer = malloc(buffer_len);
-    getgrnam_r(group, grp, buffer, buffer_len, &grp);
+    getgrnam_r(group, &grp, buffer, buffer_len, &result);
 
-    if(grp == NULL)
+    if(result == NULL)
     {
         LOGGER_CRIT("couldn't find group to drop privileges\n");
         exit(1);
     }
 
-    gid_t result = grp->gr_gid;
     free(buffer);
-    free(grp);
-    return result;
+    return result->gr_gid;
 }
 
 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
new file mode 100644
index 0000000..3ae6246
--- /dev/null
+++ b/src/helpers/find_query_param.c
@@ -0,0 +1,37 @@
+#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 68f029e..fb8be62 100644
--- a/src/helpers/get_weekday.c
+++ b/src/helpers/get_weekday.c
@@ -1,11 +1,12 @@
 #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 + 6) % 7;
+    int wday_mon_sun = (wday_sun_sat + (DAYS_PER_WEEK - 1)) % DAYS_PER_WEEK; 
     return wday_mon_sun;
 }
diff --git a/src/logger.c b/src/logger.c
index 2869668..8ba8af2 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -17,10 +17,11 @@ 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.log_level < level)
+    if(global_config->logging.level < level || level == LOG_NONE )
     {
         return;
     }
+    va_list args;
     const char *level_str;
     const char *color;
 
@@ -62,24 +63,34 @@ 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);
 
-    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);
+    size_t buffer_size = 128;
+    buffer_size += timestamp_len;
+    buffer_size += strlen(filename);
+    buffer_size += strlen(func);
+    buffer_size += strlen(msg);
 
-    //fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
+    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);
 
-    va_list args;
+    // start arg va_list and find log_len
     va_start(args, msg);
-    vsyslog(level, buffer, args);
+    size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
     va_end(args);
 
-    char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2));
-    sprintf(buffer_timed, "%s %s", timestamp_str, buffer);
+    char *log_line = malloc(sizeof(char) * (log_len + 1));
+
+    // start arg va_list again and write log_line
     va_start(args, msg);
-    vfprintf(global_config.log_file, buffer_timed, args);
-    fflush(global_config.log_file);
+    vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
     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(buffer_timed);
+    free(log_line);
 }
diff --git a/src/main.c b/src/main.c
index cc60f25..fdfe01a 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 <router.h>
-#include <logger.h>
+#include <cli.h>
 #include <config.h>
 #include <database.h>
-#include <handlers.h>
 #include <enums.h>
+#include <handlers.h>
 #include <helpers.h>
-#include <status.h>
+#include <logger.h>
 #include <models/controller.h>
+#include <router.h>
+#include <status.h>
 
 static struct mg_mgr mgr;
 
 static void
 terminate(int signum)
 {
-    LOGGER_INFO("terminating controller (%d)\n", signum);
+    LOGGER_INFO("terminating core (%d)\n", signum);
 
     mg_mgr_free(&mgr);
 
@@ -30,6 +30,7 @@ terminate(int signum)
     router_free();
     status_free();
     cache_free();
+    config_free();
 
     closelog();
 
@@ -51,54 +52,27 @@ main(int argc, const char** argv)
     signal(SIGABRT, terminate);
     signal(SIGTERM, terminate);
 
-    setlogmask(LOG_UPTO(LOG_INFO));
-
+    openlog("emgauwa-core", 0, LOG_USER);
+    setlogmask(LOG_UPTO(LOG_DEBUG));
 
     /******************** LOAD CONFIG ********************/
 
-    global_config.file = "core.ini";
-    global_config.discovery_port = 4421;
-    global_config.mqtt_port = 1885;
-    global_config.server_port = 5000;
+    config_init();
 
-    global_config.log_level = LOG_INFO;
-    global_config.log_file = stdout;
+    cli_t cli;
+    cli_parse(argc, argv, &cli);
 
-    strcpy(global_config.user, "");
-    strcpy(global_config.group, "");
+    config_load(global_config, cli.config_file);
 
-    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)
+    if(global_config->logging.file == NULL)
     {
-        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);
+        global_config->logging.file = stdout;
     }
 
-    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)
+    if(global_config->include)
     {
-        global_config.log_file = stdout;
+        config_load_directory(global_config, global_config->include);
     }
-    openlog("emgauwa-core", 0, LOG_USER);
 
 
     /******************** SETUP CONNECTION ********************/
@@ -108,20 +82,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.server_port);
+    sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.server);
     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.server_port);
+        LOGGER_CRIT("failed to bind http server to port %u\n", global_config->ports.server);
         exit(1);
     }
     mg_set_protocol_http_websocket(c_http);
 
-    sprintf(address, "tcp://0.0.0.0:%u", global_config.mqtt_port);
+    sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.mqtt);
     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.mqtt_port);
+        LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config->ports.mqtt);
         exit(1);
     }
     mg_mqtt_broker_init(&brk, NULL);
@@ -130,6 +104,11 @@ 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 807853c..3ed6048 100644
--- a/src/models/controller.c
+++ b/src/models/controller.c
@@ -35,6 +35,8 @@ 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);
@@ -50,14 +52,16 @@ 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), 16);
+                        strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), IP_LENGTH);
+                        new_controller->ip[IP_LENGTH] = '\0';
                         break;
                     default: // ignore columns not implemented
                         break;
                 }
                 break;
             case 'n': // name
-                strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
+                strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
+                new_controller->name[MAX_NAME_LENGTH] = '\0';
                 break;
             case 'p': // port
                 new_controller->port = sqlite3_column_int(stmt, i);
@@ -102,11 +106,9 @@ controller_db_select(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
-            {
-                LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
-                break;
-            }
+
+            LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
+            break;
         }
     }
     sqlite3_finalize(stmt);
@@ -117,7 +119,8 @@ controller_db_select(sqlite3_stmt *stmt)
 int
 controller_save(controller_t *controller)
 {
-    int opened_transaction = database_transaction_begin();
+    database_transaction_lock lock;
+    database_transaction_begin(&lock);
      
     sqlite3_stmt *stmt;
     if(controller->id)
@@ -142,10 +145,7 @@ controller_save(controller_t *controller)
             LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
         }
 
-        if(opened_transaction)
-        {
-            database_transaction_rollback();
-        }
+        database_transaction_rollback(&lock);
     }
     else
     {
@@ -154,10 +154,7 @@ controller_save(controller_t *controller)
             controller->id = sqlite3_last_insert_rowid(global_database);
         }
 
-        if(opened_transaction)
-        {
-            database_transaction_commit();
-        }
+        database_transaction_commit(&lock);
     }
 
     cache_invalidate_controller(controller->id);
diff --git a/src/models/junction_relay_schedule.c b/src/models/junction_relay_schedule.c
index e2b2309..e2c8411 100644
--- a/src/models/junction_relay_schedule.c
+++ b/src/models/junction_relay_schedule.c
@@ -40,14 +40,16 @@ 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 = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1;
+    size_t query_len = M_STRLEN(query_base) + (7 * (M_STRLEN(query_extender) + 1)) + 1;
     char *query = malloc(sizeof(char) * query_len + 1);
     strncpy(query, query_base, query_len);
-    query_len -= STRLEN(query_base);
+    query[query_len] = '\0';
+
+    query_len -= M_STRLEN(query_base);
     for(int i = 0; i < 7; ++i)
     {
         strncat(query, query_extender, query_len);
-        query_len -= STRLEN(query_extender);
+        query_len -= M_STRLEN(query_extender);
         char *query_divider = (i < 7 - 1) ? "," : ";";
         strncat(query, query_divider, query_len);
         query_len -= 1;
@@ -62,16 +64,18 @@ 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 false;
+        return 1;
     }
 
     sqlite3_finalize(stmt);
 
-    return true;
+    return 0;
 }
 
 int
@@ -87,3 +91,14 @@ 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 a62bed8..10700ad 100644
--- a/src/models/junction_tag.c
+++ b/src/models/junction_tag.c
@@ -67,14 +67,16 @@ 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 = STRLEN(query_base) + (count * (STRLEN(query_extender) + 1)) + 1;
-    char *query = malloc(sizeof(char) * query_len + 1);
+    size_t query_len = M_STRLEN(query_base) + (count * (M_STRLEN(query_extender) + 1)) + 1;
+    char *query = malloc(sizeof(char) * (query_len + 1));
     strncpy(query, query_base, query_len);
-    query_len -= STRLEN(query_base);
+    query[query_len] = '\0';
+
+    query_len -= M_STRLEN(query_base);
     for(int i = 0; i < count; ++i)
     {
         strncat(query, query_extender, query_len);
-        query_len -= STRLEN(query_extender);
+        query_len -= M_STRLEN(query_extender);
         char *query_divider = (i < count - 1) ? "," : ";";
         strncat(query, query_divider, query_len);
         query_len -= 1;
@@ -105,6 +107,8 @@ 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
new file mode 100644
index 0000000..00a72aa
--- /dev/null
+++ b/src/models/macro.c
@@ -0,0 +1,328 @@
+#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
new file mode 100644
index 0000000..e603c00
--- /dev/null
+++ b/src/models/macro_action.c
@@ -0,0 +1,189 @@
+#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 a5a006a..c582164 100644
--- a/src/models/period.c
+++ b/src/models/period.c
@@ -27,9 +27,8 @@ period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm)
         }
     }
 
-    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);
+    uint16_t tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10);
+    uint16_t 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 df196f0..1707d22 100644
--- a/src/models/relay.c
+++ b/src/models/relay.c
@@ -1,18 +1,19 @@
+#include <sqlite3.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sqlite3.h>
 
-#include <cache.h>
 #include <cJSON.h>
-#include <logger.h>
+#include <cache.h>
+#include <constants.h>
 #include <database.h>
-#include <status.h>
-#include <models/relay.h>
+#include <logger.h>
 #include <models/controller.h>
-#include <models/schedule.h>
-#include <models/junction_tag.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 <status.h>
 
 static int
 db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
@@ -35,6 +36,7 @@ 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++)
@@ -52,7 +54,8 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
                 switch(name[1])
                 {
                     case 'a': // name
-                        strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
+                        strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
+                        new_relay->name[MAX_NAME_LENGTH] = '\0';
                         break;
                     case 'u': // number
                         new_relay->number = sqlite3_column_int(stmt, i);
@@ -66,7 +69,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
         }
     }
 
-    memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
+    memset(new_relay->schedules, 0, sizeof(schedule_t*) * DAYS_PER_WEEK);
     relay_reload_schedules(new_relay);
 
     relay_reload_active_schedule(new_relay);
@@ -100,11 +103,9 @@ relay_db_select(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
-            {
-                LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
-                break;
-            }
+
+            LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
+            break;
         }
     }
     sqlite3_finalize(stmt);
@@ -115,7 +116,8 @@ relay_db_select(sqlite3_stmt *stmt)
 int
 relay_save(relay_t *relay)
 {
-    int opened_transaction = database_transaction_begin();
+    database_transaction_lock lock;
+    database_transaction_begin(&lock);
      
     sqlite3_stmt *stmt;
     if(relay->id)
@@ -140,10 +142,7 @@ relay_save(relay_t *relay)
             LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
         }
 
-        if(opened_transaction)
-        {
-            database_transaction_rollback();
-        }
+        database_transaction_rollback(&lock);
     }
     else
     {
@@ -156,17 +155,14 @@ relay_save(relay_t *relay)
         junction_relay_schedule_remove_for_relay(relay->id);
 
         LOGGER_DEBUG("rebuilding relay_schedule junction\n");
-        int schedule_ids[7];
-        for(int i = 0; i < 7; ++i)
+        int schedule_ids[DAYS_PER_WEEK];
+        for(int i = 0; i < DAYS_PER_WEEK; ++i)
         {
             schedule_ids[i] = relay->schedules[i]->id;
         }
         junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);
 
-        if(opened_transaction)
-        {
-            database_transaction_commit();
-        }
+        database_transaction_commit(&lock);
     }
 
     cache_invalidate_relay(relay->id, -1);
@@ -188,17 +184,16 @@ relay_reload_schedules(relay_t *relay)
 {
     schedule_t **schedules = schedule_get_relay_weekdays(relay->id);
 
-    uuid_t off_id;
-    memset(off_id, 0, sizeof(uuid_t));
-    memcpy(off_id, "off", 3);
+    uuid_t off_uid;
+    schedule_get_uid_off(off_uid);
 
     int fill_with_off = 0;
-    for(int i = 0; i < 7; ++i)
+    for(int i = 0; i < DAYS_PER_WEEK; ++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_id);
+            relay->schedules[i] = schedule_get_by_uid(off_uid);
 
             fill_with_off = 1;
         }
@@ -218,7 +213,7 @@ relay_reload_schedules(relay_t *relay)
 void
 relay_free(relay_t *relay)
 {
-    for(int i = 0; i < 7; ++i)
+    for(int i = 0; i < DAYS_PER_WEEK; ++i)
     {
         schedule_free(relay->schedules[i]);
     }
@@ -311,7 +306,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 < 7; ++i)
+    for(int i = 0; i < DAYS_PER_WEEK; ++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 abb6850..d425b9b 100644
--- a/src/models/schedule.c
+++ b/src/models/schedule.c
@@ -37,6 +37,8 @@ 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);
@@ -46,8 +48,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), 127);
-                new_schedule->name[127] = '\0';
+                strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
+                new_schedule->name[MAX_NAME_LENGTH] = '\0';
                 break;
             case 'p': // periods
                 periods_blob = sqlite3_column_blob(stmt, i);
@@ -97,11 +99,9 @@ schedule_db_select(sqlite3_stmt *stmt)
             {
                 break;
             }
-            else
-            {
-                LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
-                break;
-            }
+
+            LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
+            break;
         }
     }
     sqlite3_finalize(stmt);
@@ -112,7 +112,8 @@ schedule_db_select(sqlite3_stmt *stmt)
 int
 schedule_save(schedule_t *schedule)
 {
-    int opened_transaction = database_transaction_begin();
+    database_transaction_lock lock;
+    database_transaction_begin(&lock);
      
     sqlite3_stmt *stmt;
     if(schedule->id)
@@ -137,10 +138,7 @@ schedule_save(schedule_t *schedule)
             LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
         }
 
-        if(opened_transaction)
-        {
-            database_transaction_rollback();
-        }
+        database_transaction_rollback(&lock);
     }
     else
     {
@@ -149,10 +147,7 @@ schedule_save(schedule_t *schedule)
             schedule->id = sqlite3_last_insert_rowid(global_database);
         }
 
-        if(opened_transaction)
-        {
-            database_transaction_commit();
-        }
+        database_transaction_commit(&lock);
     }
 
     cache_invalidate_schedule(schedule->id);
@@ -182,18 +177,16 @@ schedule_remove(schedule_t *schedule)
 int
 schedule_is_protected(schedule_t *schedule)
 {
-    uuid_t tmp_uuid;
+    uuid_t tmp_uid;
 
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "off", 3);
-    if(uuid_compare(schedule->uid, tmp_uuid) == 0)
+    schedule_get_uid_off(tmp_uid);
+    if(uuid_compare(schedule->uid, tmp_uid) == 0)
     {
         return 1;
     }
 
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "on", 2);
-    if(uuid_compare(schedule->uid, tmp_uuid) == 0)
+    schedule_get_uid_on(tmp_uid);
+    if(uuid_compare(schedule->uid, tmp_uid) == 0)
     {
         return 1;
     }
@@ -285,9 +278,10 @@ schedule_to_json(schedule_t *schedule)
             continue;
         }
 
-        char start_str[8], end_str[8];
+        char start_str[8];
+        char 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);
@@ -364,11 +358,10 @@ schedule_get_by_id_or_off(int id)
         return result;
     }
     
-    uuid_t tmp_uuid;
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "off", 3);
+    uuid_t tmp_uid;
+    schedule_get_uid_off(tmp_uid);
 
-    return schedule_get_by_uid(tmp_uuid);
+    return schedule_get_by_uid(tmp_uid);
 }
 
 schedule_t*
@@ -409,11 +402,10 @@ schedule_get_by_uid_or_off(uuid_t uid)
         return result;
     }
     
-    uuid_t tmp_uuid;
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "off", 3);
+    uuid_t tmp_uid;
+    schedule_get_uid_off(tmp_uid);
 
-    return schedule_get_by_uid(tmp_uuid);
+    return schedule_get_by_uid(tmp_uid);
 }
 
 schedule_t*
@@ -464,14 +456,12 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
 {
     if(strcmp("off", uid_str) == 0)
     {
-        memset(result, 0, sizeof(uuid_t));
-        memcpy(result, "off", 3);
+        schedule_get_uid_off(result);
         return 0;
     }
     if(strcmp("on", uid_str) == 0)
     {
-        memset(result, 0, sizeof(uuid_t));
-        memcpy(result, "on", 2);
+        schedule_get_uid_on(result);
         return 0;
     }
 
@@ -485,23 +475,37 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
 void
 schedule_uid_unparse(const uuid_t uid, char *result)
 {
-    uuid_t tmp_uuid;
+    uuid_t tmp_uid;
 
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "off", 3);
-    if(uuid_compare(uid, tmp_uuid) == 0)
+    schedule_get_uid_off(tmp_uid);
+    if(uuid_compare(uid, tmp_uid) == 0)
     {
-        strcpy(result, "off");
+        strncpy(result, "off", 4);
+        result[3] = '\0';
         return;
     }
 
-    memset(tmp_uuid, 0, sizeof(uuid_t));
-    memcpy(tmp_uuid, "on", 2);
-    if(uuid_compare(uid, tmp_uuid) == 0)
+    schedule_get_uid_on(tmp_uid);
+    if(uuid_compare(uid, tmp_uid) == 0)
     {
-        strcpy(result, "on");
+        strncpy(result, "on", 3);
+        result[2] = '\0';
         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 6c74428..dfc68db 100644
--- a/src/router.c
+++ b/src/router.c
@@ -10,6 +10,7 @@
 #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];
@@ -71,7 +72,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_POST, api_v1_controllers_discover_POST);
+    router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_PUT, api_v1_controllers_discover_PUT);
     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);
@@ -90,6 +91,13 @@ 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);
 }
 
@@ -146,7 +154,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);
+    strncpy(endpoint->route_keeper, route, route_len + 1);
     endpoint->route_keeper[route_len] = '\0';
 
     int route_part = 0;
@@ -305,7 +313,6 @@ 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;
@@ -333,8 +340,10 @@ 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)
         {
-            char *arg_value_str = malloc(sizeof(char) * (strlen(best_endpoint->args[i].value.v_str) + 1));
+            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));
             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
deleted file mode 100644
index d3577f6..0000000
--- a/tests/controller.testing.ini
+++ /dev/null
@@ -1,65 +0,0 @@
-[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
deleted file mode 100644
index c3710a1..0000000
--- a/tests/core.testing.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-[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
new file mode 100644
index 0000000..5b541e4
--- /dev/null
+++ b/tests/emgauwa-controller-testing.conf
@@ -0,0 +1,66 @@
+[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
new file mode 100644
index 0000000..c3896b2
--- /dev/null
+++ b/tests/emgauwa-core-testing.conf
@@ -0,0 +1,17 @@
+[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
new file mode 100644
index 0000000..ac1dfee
--- /dev/null
+++ b/tests/emgauwa-core-testing.conf.d/conf_d_working.conf
@@ -0,0 +1,3 @@
+[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 cbfbdb1..15b1d18 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -1,14 +1,9 @@
 #!/usr/bin/env sh
 
 source_dir=$PWD
-working_dir=$PWD/testing_latest
-working_bak=$PWD/testing_bak
+working_dir=$PWD/testing
 
-alias valgrind_emgauwa="valgrind $2 --log-file=$working_dir/valgrind.log"
-
-
-rm -rf $working_bak
-[ -d $working_dir ] && mv $working_dir $working_bak
+alias valgrind_emgauwa="valgrind -s $2 --log-file=$working_dir/valgrind.log"
 
 mkdir -p $working_dir
 cd $working_dir
@@ -17,15 +12,18 @@ target_branch=$(git rev-parse --abbrev-ref HEAD)
 
 if [ -z "$EMGAUWA_CONTROLLER_EXE" ]
 then
-    git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git controller || exit
-    cd ./controller
+    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 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 build
+    mkdir -p build
     cd build
 
     cmake -DWIRING_PI_DEBUG=on .. >/dev/null
@@ -35,20 +33,16 @@ fi
 
 echo "Emgauwa controller: $($EMGAUWA_CONTROLLER_EXE --version)"
 
-$EMGAUWA_CONTROLLER_EXE start -c $source_dir/controller.testing.ini >$working_dir/controller.log 2>&1 &
+$EMGAUWA_CONTROLLER_EXE -c $source_dir/emgauwa-controller-testing.conf >$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
 
-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 &
+valgrind_emgauwa $working_dir/core -c $source_dir/emgauwa-core-testing.conf >>$working_dir/core.log 2>&1 &
 core_id=$!
 
 
@@ -68,4 +62,12 @@ 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
new file mode 100644
index 0000000..8eff061
--- /dev/null
+++ b/tests/tavern_tests/0.1.test_basics.tavern.yaml
@@ -0,0 +1,25 @@
+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 a48ea17..3bce298 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: POST
+    method: PUT
     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: POST
+    method: PUT
     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 f0d1750..285d2d5 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 relays, check length"
+- name: "[controller_relays_basic] get controller relay"
   request:
     method: GET
     url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/5"
@@ -40,3 +40,69 @@ 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 d4ef67f..9004442 100644
--- a/tests/tavern_tests/3.0.tags.tavern.yaml
+++ b/tests/tavern_tests/3.0.tags.tavern.yaml
@@ -98,6 +98,13 @@ 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
@@ -106,3 +113,56 @@ 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 1e907e5..6a3004a 100644
--- a/tests/tavern_utils/validate_tag.py
+++ b/tests/tavern_utils/validate_tag.py
@@ -11,24 +11,9 @@ def multiple(response):
     for tag in response.json():
         _verify_single(tag)
 
-#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"
+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"
diff --git a/vendor/confini.c b/vendor/confini.c
deleted file mode 100644
index 25353a7..0000000
--- a/vendor/confini.c
+++ /dev/null
@@ -1,5016 +0,0 @@
-/*  -*- 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
deleted file mode 100644
index c4c0068..0000000
--- a/vendor/confini.h
+++ /dev/null
@@ -1,547 +0,0 @@
-/*  -*- 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 7808e91..aac725e 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 *md, cs_md5_ctx *c);
+void cs_md5_final(unsigned char digest[16], cs_md5_ctx *c);
 
 #ifdef __cplusplus
 }
diff --git a/vendor/toml.c b/vendor/toml.c
new file mode 100644
index 0000000..98f765b
--- /dev/null
+++ b/vendor/toml.c
@@ -0,0 +1,2274 @@
+/*
+
+  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
new file mode 100644
index 0000000..19f6f64
--- /dev/null
+++ b/vendor/toml.h
@@ -0,0 +1,175 @@
+/*
+  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 */