summaryrefslogtreecommitdiffstats
path: root/src/lib-settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-settings')
-rw-r--r--src/lib-settings/Makefile.am40
-rw-r--r--src/lib-settings/Makefile.in870
-rw-r--r--src/lib-settings/settings-parser.c2226
-rw-r--r--src/lib-settings/settings-parser.h281
-rw-r--r--src/lib-settings/settings.c434
-rw-r--r--src/lib-settings/settings.h73
-rw-r--r--src/lib-settings/test-settings-parser.c340
-rw-r--r--src/lib-settings/test-settings.c165
8 files changed, 4429 insertions, 0 deletions
diff --git a/src/lib-settings/Makefile.am b/src/lib-settings/Makefile.am
new file mode 100644
index 0000000..30eeff5
--- /dev/null
+++ b/src/lib-settings/Makefile.am
@@ -0,0 +1,40 @@
+noinst_LTLIBRARIES = libsettings.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test
+
+libsettings_la_SOURCES = \
+ settings.c \
+ settings-parser.c
+
+headers = \
+ settings.h \
+ settings-parser.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-settings-parser \
+ test-settings
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ libsettings.la \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_settings_parser_SOURCES = test-settings-parser.c
+test_settings_parser_LDADD = $(test_libs)
+test_settings_parser_DEPENDENCIES = $(test_libs)
+
+test_settings_SOURCES = test-settings.c
+test_settings_LDADD = $(test_libs)
+test_settings_DEPENDENCIES = $(test_libs)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-settings/Makefile.in b/src/lib-settings/Makefile.in
new file mode 100644
index 0000000..ae5caa7
--- /dev/null
+++ b/src/lib-settings/Makefile.in
@@ -0,0 +1,870 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib-settings
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-settings-parser$(EXEEXT) test-settings$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsettings_la_LIBADD =
+am_libsettings_la_OBJECTS = settings.lo settings-parser.lo
+libsettings_la_OBJECTS = $(am_libsettings_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_settings_OBJECTS = test-settings.$(OBJEXT)
+test_settings_OBJECTS = $(am_test_settings_OBJECTS)
+am_test_settings_parser_OBJECTS = test-settings-parser.$(OBJEXT)
+test_settings_parser_OBJECTS = $(am_test_settings_parser_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/settings-parser.Plo \
+ ./$(DEPDIR)/settings.Plo ./$(DEPDIR)/test-settings-parser.Po \
+ ./$(DEPDIR)/test-settings.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsettings_la_SOURCES) $(test_settings_SOURCES) \
+ $(test_settings_parser_SOURCES)
+DIST_SOURCES = $(libsettings_la_SOURCES) $(test_settings_SOURCES) \
+ $(test_settings_parser_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsettings.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test
+
+libsettings_la_SOURCES = \
+ settings.c \
+ settings-parser.c
+
+headers = \
+ settings.h \
+ settings-parser.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-settings-parser \
+ test-settings
+
+test_libs = \
+ libsettings.la \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_settings_parser_SOURCES = test-settings-parser.c
+test_settings_parser_LDADD = $(test_libs)
+test_settings_parser_DEPENDENCIES = $(test_libs)
+test_settings_SOURCES = test-settings.c
+test_settings_LDADD = $(test_libs)
+test_settings_DEPENDENCIES = $(test_libs)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-settings/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-settings/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsettings.la: $(libsettings_la_OBJECTS) $(libsettings_la_DEPENDENCIES) $(EXTRA_libsettings_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsettings_la_OBJECTS) $(libsettings_la_LIBADD) $(LIBS)
+
+test-settings$(EXEEXT): $(test_settings_OBJECTS) $(test_settings_DEPENDENCIES) $(EXTRA_test_settings_DEPENDENCIES)
+ @rm -f test-settings$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_settings_OBJECTS) $(test_settings_LDADD) $(LIBS)
+
+test-settings-parser$(EXEEXT): $(test_settings_parser_OBJECTS) $(test_settings_parser_DEPENDENCIES) $(EXTRA_test_settings_parser_DEPENDENCIES)
+ @rm -f test-settings-parser$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_settings_parser_OBJECTS) $(test_settings_parser_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-settings-parser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-settings.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/settings-parser.Plo
+ -rm -f ./$(DEPDIR)/settings.Plo
+ -rm -f ./$(DEPDIR)/test-settings-parser.Po
+ -rm -f ./$(DEPDIR)/test-settings.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/settings-parser.Plo
+ -rm -f ./$(DEPDIR)/settings.Plo
+ -rm -f ./$(DEPDIR)/test-settings-parser.Po
+ -rm -f ./$(DEPDIR)/test-settings.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-settings/settings-parser.c b/src/lib-settings/settings-parser.c
new file mode 100644
index 0000000..7b63b30
--- /dev/null
+++ b/src/lib-settings/settings-parser.c
@@ -0,0 +1,2226 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "net.h"
+#include "istream.h"
+#include "env-util.h"
+#include "execv-const.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "settings-parser.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')
+
+struct setting_link {
+ struct setting_link *parent;
+ const struct setting_parser_info *info;
+
+ const char *full_key;
+
+ /* Points to array inside parent->set_struct.
+ SET_DEFLIST : array of set_structs
+ SET_STRLIST : array of const_strings */
+ ARRAY_TYPE(void_array) *array;
+ /* Pointer to structure containing the values */
+ void *set_struct;
+ /* Pointer to structure containing non-zero values for settings that
+ have been changed. */
+ void *change_struct;
+ /* SET_DEFLIST: array of change_structs */
+ ARRAY_TYPE(void_array) *change_array;
+};
+
+struct setting_parser_context {
+ pool_t set_pool, parser_pool;
+ enum settings_parser_flags flags;
+ bool str_vars_are_expanded;
+
+ struct setting_link *roots;
+ unsigned int root_count;
+ HASH_TABLE(char *, struct setting_link *) links;
+
+ unsigned int linenum;
+ const char *error;
+ const struct setting_parser_info *prev_info;
+};
+
+static const struct setting_parser_info strlist_info = {
+ .module_name = NULL,
+ .defines = NULL,
+ .defaults = NULL,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = 0,
+
+ .parent_offset = SIZE_MAX
+};
+
+HASH_TABLE_DEFINE_TYPE(setting_link, struct setting_link *,
+ struct setting_link *);
+
+static void
+setting_parser_copy_defaults(struct setting_parser_context *ctx,
+ const struct setting_parser_info *info,
+ struct setting_link *link);
+static int
+settings_apply(struct setting_link *dest_link,
+ const struct setting_link *src_link,
+ pool_t pool, const char **conflict_key_r);
+
+struct setting_parser_context *
+settings_parser_init(pool_t set_pool, const struct setting_parser_info *root,
+ enum settings_parser_flags flags)
+{
+ return settings_parser_init_list(set_pool, &root, 1, flags);
+}
+
+static void
+copy_unique_defaults(struct setting_parser_context *ctx,
+ const struct setting_define *def,
+ struct setting_link *link)
+{
+ ARRAY_TYPE(void_array) *arr =
+ STRUCT_MEMBER_P(link->set_struct, def->offset);
+ ARRAY_TYPE(void_array) *carr = NULL;
+ struct setting_link *new_link;
+ struct setting_parser_info info;
+ const char *const *keyp, *key, *prefix;
+ void *const *children;
+ void *new_set, *new_changes = NULL;
+ char *full_key;
+ unsigned int i, count;
+
+ if (!array_is_created(arr))
+ return;
+
+ children = array_get(arr, &count);
+ if (link->change_struct != NULL) {
+ carr = STRUCT_MEMBER_P(link->change_struct, def->offset);
+ i_assert(!array_is_created(carr));
+ p_array_init(carr, ctx->set_pool, count + 4);
+ }
+ p_array_init(arr, ctx->set_pool, count + 4);
+
+ i_zero(&info);
+ info = *def->list_info;
+
+ for (i = 0; i < count; i++) T_BEGIN {
+ new_set = p_malloc(ctx->set_pool, info.struct_size);
+ array_push_back(arr, &new_set);
+
+ if (link->change_struct != NULL) {
+ i_assert(carr != NULL);
+ new_changes = p_malloc(ctx->set_pool, info.struct_size);
+ array_push_back(carr, &new_changes);
+ }
+
+ keyp = CONST_PTR_OFFSET(children[i], info.type_offset);
+ key = settings_section_escape(*keyp);
+
+ new_link = p_new(ctx->set_pool, struct setting_link, 1);
+ prefix = link->full_key == NULL ?
+ t_strconcat(def->key, SETTINGS_SEPARATOR_S, NULL) :
+ t_strconcat(link->full_key, SETTINGS_SEPARATOR_S,
+ def->key, SETTINGS_SEPARATOR_S,NULL);
+ full_key = p_strconcat(ctx->set_pool, prefix, key, NULL);
+ new_link->full_key = full_key;
+ new_link->parent = link;
+ new_link->info = def->list_info;
+ new_link->array = arr;
+ new_link->change_array = carr;
+ new_link->set_struct = new_set;
+ new_link->change_struct = new_changes;
+ i_assert(hash_table_lookup(ctx->links, full_key) == NULL);
+ hash_table_insert(ctx->links, full_key, new_link);
+
+ info.defaults = children[i];
+ setting_parser_copy_defaults(ctx, &info, new_link);
+ } T_END;
+}
+
+static void
+setting_parser_copy_defaults(struct setting_parser_context *ctx,
+ const struct setting_parser_info *info,
+ struct setting_link *link)
+{
+ const struct setting_define *def;
+ const char *p, **strp;
+
+ if (info->defaults == NULL)
+ return;
+
+ memcpy(link->set_struct, info->defaults, info->struct_size);
+ for (def = info->defines; def->key != NULL; def++) {
+ switch (def->type) {
+ case SET_ENUM: {
+ /* fix enums by dropping everything after the
+ first ':' */
+ strp = STRUCT_MEMBER_P(link->set_struct, def->offset);
+ p = strchr(*strp, ':');
+ if (p != NULL)
+ *strp = p_strdup_until(ctx->set_pool, *strp, p);
+ break;
+ }
+ case SET_STR_VARS: {
+ /* insert the unexpanded-character */
+ strp = STRUCT_MEMBER_P(link->set_struct, def->offset);
+ if (*strp != NULL) {
+ *strp = p_strconcat(ctx->set_pool,
+ SETTING_STRVAR_UNEXPANDED,
+ *strp, NULL);
+ }
+ break;
+ }
+ case SET_DEFLIST_UNIQUE:
+ copy_unique_defaults(ctx, def, link);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+struct setting_parser_context *
+settings_parser_init_list(pool_t set_pool,
+ const struct setting_parser_info *const *roots,
+ unsigned int count, enum settings_parser_flags flags)
+{
+ struct setting_parser_context *ctx;
+ unsigned int i;
+ pool_t parser_pool;
+
+ i_assert(count > 0);
+
+ parser_pool = pool_alloconly_create(MEMPOOL_GROWING"settings parser",
+ 1024);
+ ctx = p_new(parser_pool, struct setting_parser_context, 1);
+ ctx->set_pool = set_pool;
+ ctx->parser_pool = parser_pool;
+ ctx->flags = flags;
+ /* use case-insensitive comparisons. this is mainly because settings
+ may go through environment variables where their keys get
+ uppercased. of course the alternative would be to not uppercase
+ environment. probably doesn't make much difference which way is
+ chosen. */
+ hash_table_create(&ctx->links, ctx->parser_pool, 0,
+ strcase_hash, strcasecmp);
+
+ ctx->root_count = count;
+ ctx->roots = p_new(ctx->parser_pool, struct setting_link, count);
+ for (i = 0; i < count; i++) {
+ ctx->roots[i].info = roots[i];
+ if (roots[i]->struct_size == 0)
+ continue;
+
+ ctx->roots[i].set_struct =
+ p_malloc(ctx->set_pool, roots[i]->struct_size);
+ if ((flags & SETTINGS_PARSER_FLAG_TRACK_CHANGES) != 0) {
+ ctx->roots[i].change_struct =
+ p_malloc(ctx->set_pool, roots[i]->struct_size);
+ }
+ setting_parser_copy_defaults(ctx, roots[i], &ctx->roots[i]);
+ }
+
+ pool_ref(ctx->set_pool);
+ return ctx;
+}
+
+void settings_parser_deinit(struct setting_parser_context **_ctx)
+{
+ struct setting_parser_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ hash_table_destroy(&ctx->links);
+ pool_unref(&ctx->set_pool);
+ pool_unref(&ctx->parser_pool);
+}
+
+void *settings_parser_get(struct setting_parser_context *ctx)
+{
+ i_assert(ctx->root_count == 1);
+
+ return ctx->roots[0].set_struct;
+}
+
+void **settings_parser_get_list(const struct setting_parser_context *ctx)
+{
+ unsigned int i;
+ void **sets;
+
+ sets = t_new(void *, ctx->root_count + 1);
+ for (i = 0; i < ctx->root_count; i++)
+ sets[i] = ctx->roots[i].set_struct;
+ return sets;
+}
+
+void *settings_parser_get_changes(struct setting_parser_context *ctx)
+{
+ i_assert(ctx->root_count == 1);
+
+ return ctx->roots[0].change_struct;
+}
+
+const struct setting_parser_info *const *
+settings_parser_get_roots(const struct setting_parser_context *ctx)
+{
+ const struct setting_parser_info **infos;
+ unsigned int i;
+
+ infos = t_new(const struct setting_parser_info *, ctx->root_count + 1);
+ for (i = 0; i < ctx->root_count; i++)
+ infos[i] = ctx->roots[i].info;
+ return infos;
+}
+
+const char *settings_parser_get_error(struct setting_parser_context *ctx)
+{
+ return ctx->error;
+}
+
+static const struct setting_define *
+setting_define_find(const struct setting_parser_info *info, const char *key)
+{
+ const struct setting_define *list;
+
+ for (list = info->defines; list->key != NULL; list++) {
+ if (strcmp(list->key, key) == 0)
+ return list;
+ }
+ return NULL;
+}
+
+static int
+get_bool(struct setting_parser_context *ctx, const char *value, bool *result_r)
+{
+ /* FIXME: eventually we'd want to support only yes/no */
+ if (strcasecmp(value, "yes") == 0 ||
+ strcasecmp(value, "y") == 0 || strcmp(value, "1") == 0)
+ *result_r = TRUE;
+ else if (strcasecmp(value, "no") == 0)
+ *result_r = FALSE;
+ else {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Invalid boolean value: %s (use yes or no)", value);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+get_uint(struct setting_parser_context *ctx, const char *value,
+ unsigned int *result_r)
+{
+ if (str_to_uint(value, result_r) < 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Invalid number %s: %s", value,
+ str_num_error(value));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+get_octal(struct setting_parser_context *ctx, const char *value,
+ unsigned int *result_r)
+{
+ unsigned long long octal;
+
+ if (*value != '0')
+ return get_uint(ctx, value, result_r);
+
+ if (str_to_ullong_oct(value, &octal) < 0) {
+ ctx->error = p_strconcat(ctx->parser_pool, "Invalid number: ",
+ value, NULL);
+ return -1;
+ }
+ *result_r = (unsigned int)octal;
+ return 0;
+}
+
+static int settings_get_time_full(const char *str, unsigned int *interval_r,
+ bool milliseconds, const char **error_r)
+{
+ uintmax_t num, multiply = milliseconds ? 1000 : 1;
+ const char *p;
+
+ if (str_parse_uintmax(str, &num, &p) < 0) {
+ *error_r = t_strconcat("Invalid time interval: ", str, NULL);
+ return -1;
+ }
+ while (*p == ' ') p++;
+ if (*p == '\0' && num != 0) {
+ *error_r = t_strdup_printf("Time interval '%s' is missing units "
+ "(add e.g. 's' for seconds)", str);
+ return -1;
+ }
+ switch (i_toupper(*p)) {
+ case 'S':
+ multiply *= 1;
+ if (strncasecmp(p, "secs", strlen(p)) == 0 ||
+ strncasecmp(p, "seconds", strlen(p)) == 0)
+ p = "";
+ break;
+ case 'M':
+ multiply *= 60;
+ if (strncasecmp(p, "mins", strlen(p)) == 0 ||
+ strncasecmp(p, "minutes", strlen(p)) == 0)
+ p = "";
+ else if (strncasecmp(p, "msecs", strlen(p)) == 0 ||
+ strncasecmp(p, "mseconds", strlen(p)) == 0 ||
+ strncasecmp(p, "millisecs", strlen(p)) == 0 ||
+ strncasecmp(p, "milliseconds", strlen(p)) == 0) {
+ if (milliseconds || (num % 1000) == 0) {
+ if (!milliseconds) {
+ /* allow ms also for seconds, as long
+ as it's divisible by seconds */
+ num /= 1000;
+ }
+ multiply = 1;
+ p = "";
+ break;
+ }
+ *error_r = t_strdup_printf(
+ "Milliseconds not supported for this setting: %s", str);
+ return -1;
+ }
+ break;
+ case 'H':
+ multiply *= 60*60;
+ if (strncasecmp(p, "hours", strlen(p)) == 0)
+ p = "";
+ break;
+ case 'D':
+ multiply *= 60*60*24;
+ if (strncasecmp(p, "days", strlen(p)) == 0)
+ p = "";
+ break;
+ case 'W':
+ multiply *= 60*60*24*7;
+ if (strncasecmp(p, "weeks", strlen(p)) == 0)
+ p = "";
+ break;
+ }
+
+ if (*p != '\0') {
+ *error_r = t_strconcat("Invalid time interval: ", str, NULL);
+ return -1;
+ }
+ if (num > UINT_MAX / multiply) {
+ *error_r = t_strconcat("Time interval is too large: ",
+ str, NULL);
+ return -1;
+ }
+ *interval_r = num * multiply;
+ return 0;
+}
+
+int settings_get_time(const char *str, unsigned int *secs_r,
+ const char **error_r)
+{
+ return settings_get_time_full(str, secs_r, FALSE, error_r);
+}
+
+int settings_get_time_msecs(const char *str, unsigned int *msecs_r,
+ const char **error_r)
+{
+ return settings_get_time_full(str, msecs_r, TRUE, error_r);
+}
+
+int settings_get_size(const char *str, uoff_t *bytes_r,
+ const char **error_r)
+{
+ uintmax_t num, multiply = 1;
+ const char *p;
+
+ if (str_parse_uintmax(str, &num, &p) < 0) {
+ *error_r = t_strconcat("Invalid size: ", str, NULL);
+ return -1;
+ }
+ while (*p == ' ') p++;
+ switch (i_toupper(*p)) {
+ case 'B':
+ multiply = 1;
+ p += 1;
+ break;
+ case 'K':
+ multiply = 1024;
+ p += 1;
+ break;
+ case 'M':
+ multiply = 1024*1024;
+ p += 1;
+ break;
+ case 'G':
+ multiply = 1024*1024*1024;
+ p += 1;
+ break;
+ case 'T':
+ multiply = 1024ULL*1024*1024*1024;
+ p += 1;
+ break;
+ }
+
+ if (multiply > 1) {
+ /* Allow: k, ki, kiB */
+ if (i_toupper(*p) == 'I')
+ p++;
+ if (i_toupper(*p) == 'B')
+ p++;
+ }
+ if (*p != '\0') {
+ *error_r = t_strconcat("Invalid size: ", str, NULL);
+ return -1;
+ }
+ if (num > (UOFF_T_MAX) / multiply) {
+ *error_r = t_strconcat("Size is too large: ", str, NULL);
+ return -1;
+ }
+ *bytes_r = num * multiply;
+ return 0;
+}
+
+static int get_enum(struct setting_parser_context *ctx, const char *value,
+ char **result_r, const char *allowed_values)
+{
+ const char *p;
+
+ while (allowed_values != NULL) {
+ p = strchr(allowed_values, ':');
+ if (p == NULL) {
+ if (strcmp(allowed_values, value) == 0)
+ break;
+
+ ctx->error = p_strconcat(ctx->parser_pool,
+ "Invalid value: ",
+ value, NULL);
+ return -1;
+ }
+
+ if (strncmp(allowed_values, value, p - allowed_values) == 0 &&
+ value[p - allowed_values] == '\0')
+ break;
+
+ allowed_values = p + 1;
+ }
+
+ *result_r = p_strdup(ctx->set_pool, value);
+ return 0;
+}
+
+static void
+setting_link_init_set_struct(struct setting_parser_context *ctx,
+ struct setting_link *link)
+{
+ void *ptr;
+
+ link->set_struct = p_malloc(ctx->set_pool, link->info->struct_size);
+ if ((ctx->flags & SETTINGS_PARSER_FLAG_TRACK_CHANGES) != 0) {
+ link->change_struct =
+ p_malloc(ctx->set_pool, link->info->struct_size);
+ array_push_back(link->change_array, &link->change_struct);
+ }
+
+ setting_parser_copy_defaults(ctx, link->info, link);
+ array_push_back(link->array, &link->set_struct);
+
+ if (link->info->parent_offset != SIZE_MAX && link->parent != NULL) {
+ ptr = STRUCT_MEMBER_P(link->set_struct,
+ link->info->parent_offset);
+ *((void **)ptr) = link->parent->set_struct;
+ }
+}
+
+static int ATTR_NULL(2)
+setting_link_add(struct setting_parser_context *ctx,
+ const struct setting_define *def,
+ const struct setting_link *link_copy, char *key)
+{
+ struct setting_link *link;
+
+ link = hash_table_lookup(ctx->links, key);
+ if (link != NULL) {
+ if (link->parent == link_copy->parent &&
+ link->info == link_copy->info &&
+ (def == NULL || def->type == SET_DEFLIST_UNIQUE))
+ return 0;
+ ctx->error = p_strconcat(ctx->parser_pool, key,
+ " already exists", NULL);
+ return -1;
+ }
+
+ link = p_new(ctx->parser_pool, struct setting_link, 1);
+ *link = *link_copy;
+ link->full_key = key;
+ i_assert(hash_table_lookup(ctx->links, key) == NULL);
+ hash_table_insert(ctx->links, key, link);
+
+ if (link->info->struct_size != 0)
+ setting_link_init_set_struct(ctx, link);
+ return 0;
+}
+
+static int ATTR_NULL(3, 8)
+get_deflist(struct setting_parser_context *ctx, struct setting_link *parent,
+ const struct setting_define *def,
+ const struct setting_parser_info *info,
+ const char *key, const char *value, ARRAY_TYPE(void_array) *result,
+ ARRAY_TYPE(void_array) *change_result)
+{
+ struct setting_link new_link;
+ const char *const *list;
+ char *full_key;
+
+ i_assert(info->defines != NULL || info == &strlist_info);
+
+ if (!array_is_created(result))
+ p_array_init(result, ctx->set_pool, 5);
+ if (change_result != NULL && !array_is_created(change_result))
+ p_array_init(change_result, ctx->set_pool, 5);
+
+ i_zero(&new_link);
+ new_link.parent = parent;
+ new_link.info = info;
+ new_link.array = result;
+ new_link.change_array = change_result;
+
+ if (info == &strlist_info) {
+ /* there are no sections below strlist, so allow referencing it
+ without the key (e.g. plugin/foo instead of plugin/0/foo) */
+ full_key = p_strdup(ctx->parser_pool, key);
+ if (setting_link_add(ctx, def, &new_link, full_key) < 0)
+ return -1;
+ }
+
+ list = t_strsplit(value, ",\t ");
+ for (; *list != NULL; list++) {
+ if (**list == '\0')
+ continue;
+
+ full_key = p_strconcat(ctx->parser_pool, key,
+ SETTINGS_SEPARATOR_S, *list, NULL);
+ if (setting_link_add(ctx, def, &new_link, full_key) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+get_in_port_zero(struct setting_parser_context *ctx, const char *value,
+ in_port_t *result_r)
+{
+ if (net_str2port_zero(value, result_r) < 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Invalid port number %s", value);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+settings_parse(struct setting_parser_context *ctx, struct setting_link *link,
+ const struct setting_define *def,
+ const char *key, const char *value)
+{
+ void *ptr, *change_ptr;
+ const void *ptr2;
+ const char *error;
+
+ while (def->type == SET_ALIAS) {
+ i_assert(def != link->info->defines);
+ def--;
+ }
+
+ ctx->prev_info = link->info;
+
+ if (link->set_struct == NULL)
+ setting_link_init_set_struct(ctx, link);
+
+ change_ptr = link->change_struct == NULL ? NULL :
+ STRUCT_MEMBER_P(link->change_struct, def->offset);
+
+ ptr = STRUCT_MEMBER_P(link->set_struct, def->offset);
+ switch (def->type) {
+ case SET_BOOL:
+ if (get_bool(ctx, value, (bool *)ptr) < 0)
+ return -1;
+ break;
+ case SET_UINT:
+ if (get_uint(ctx, value, (unsigned int *)ptr) < 0)
+ return -1;
+ break;
+ case SET_UINT_OCT:
+ if (get_octal(ctx, value, (unsigned int *)ptr) < 0)
+ return -1;
+ break;
+ case SET_TIME:
+ if (settings_get_time(value, (unsigned int *)ptr, &error) < 0) {
+ ctx->error = p_strdup(ctx->parser_pool, error);
+ return -1;
+ }
+ break;
+ case SET_TIME_MSECS:
+ if (settings_get_time_msecs(value, (unsigned int *)ptr, &error) < 0) {
+ ctx->error = p_strdup(ctx->parser_pool, error);
+ return -1;
+ }
+ break;
+ case SET_SIZE:
+ if (settings_get_size(value, (uoff_t *)ptr, &error) < 0) {
+ ctx->error = p_strdup(ctx->parser_pool, error);
+ return -1;
+ }
+ break;
+ case SET_IN_PORT:
+ if (get_in_port_zero(ctx, value, (in_port_t *)ptr) < 0)
+ return -1;
+ break;
+ case SET_STR:
+ *((char **)ptr) = p_strdup(ctx->set_pool, value);
+ break;
+ case SET_STR_VARS:
+ *((char **)ptr) = p_strconcat(ctx->set_pool,
+ ctx->str_vars_are_expanded ?
+ SETTING_STRVAR_EXPANDED :
+ SETTING_STRVAR_UNEXPANDED,
+ value, NULL);
+ break;
+ case SET_ENUM:
+ /* get the available values from default string */
+ i_assert(link->info->defaults != NULL);
+ ptr2 = CONST_STRUCT_MEMBER_P(link->info->defaults, def->offset);
+ if (get_enum(ctx, value, (char **)ptr,
+ *(const char *const *)ptr2) < 0)
+ return -1;
+ break;
+ case SET_DEFLIST:
+ case SET_DEFLIST_UNIQUE:
+ ctx->prev_info = def->list_info;
+ return get_deflist(ctx, link, def, def->list_info,
+ key, value, (ARRAY_TYPE(void_array) *)ptr,
+ (ARRAY_TYPE(void_array) *)change_ptr);
+ case SET_STRLIST: {
+ ctx->prev_info = &strlist_info;
+ if (get_deflist(ctx, link, NULL, &strlist_info, key, value,
+ (ARRAY_TYPE(void_array) *)ptr, NULL) < 0)
+ return -1;
+ break;
+ }
+ case SET_ALIAS:
+ i_unreached();
+ break;
+ }
+
+ if (change_ptr != NULL)
+ *((char *)change_ptr) = 1;
+ return 0;
+}
+
+static bool
+settings_find_key_nth(struct setting_parser_context *ctx, const char *key,
+ unsigned int *n, const struct setting_define **def_r,
+ struct setting_link **link_r)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+ const char *end, *parent_key;
+ unsigned int i;
+
+ /* try to find from roots */
+ for (i = *n; i < ctx->root_count; i++) {
+ def = setting_define_find(ctx->roots[i].info, key);
+ if (def != NULL) {
+ *n = i + 1;
+ *def_r = def;
+ *link_r = &ctx->roots[i];
+ return TRUE;
+ }
+ }
+ if (*n > ctx->root_count)
+ return FALSE;
+ *n += 1;
+
+ /* try to find from links */
+ end = strrchr(key, SETTINGS_SEPARATOR);
+ if (end == NULL)
+ return FALSE;
+
+ parent_key = t_strdup_until(key, end);
+ link = hash_table_lookup(ctx->links, parent_key);
+ if (link == NULL) {
+ /* maybe this is the first strlist value */
+ unsigned int parent_n = 0;
+ const struct setting_define *parent_def;
+ struct setting_link *parent_link;
+
+ if (!settings_find_key_nth(ctx, parent_key, &parent_n,
+ &parent_def, &parent_link))
+ return FALSE;
+ if (parent_def == NULL) {
+ /* we'll get here with e.g. "plugin/a/b=val".
+ not sure if we should ever do anything here.. */
+ if (parent_link->full_key == NULL ||
+ strcmp(parent_link->full_key, parent_key) != 0)
+ return FALSE;
+ } else {
+ if (parent_def->type != SET_STRLIST)
+ return FALSE;
+ }
+
+ /* setting parent_key=0 adds it to links list */
+ if (settings_parse_keyvalue(ctx, parent_key, "0") <= 0)
+ return FALSE;
+
+ link = hash_table_lookup(ctx->links, parent_key);
+ i_assert(link != NULL);
+ }
+
+ *link_r = link;
+ if (link->info == &strlist_info) {
+ *def_r = NULL;
+ return TRUE;
+ } else {
+ *def_r = setting_define_find(link->info, end + 1);
+ return *def_r != NULL;
+ }
+}
+
+static bool
+settings_find_key(struct setting_parser_context *ctx, const char *key,
+ const struct setting_define **def_r,
+ struct setting_link **link_r)
+{
+ unsigned int n = 0;
+
+ return settings_find_key_nth(ctx, key, &n, def_r, link_r);
+}
+
+static void
+settings_parse_strlist(struct setting_parser_context *ctx,
+ struct setting_link *link,
+ const char *key, const char *value)
+{
+ void *const *items;
+ void *vkey, *vvalue;
+ unsigned int i, count;
+
+ key = strrchr(key, SETTINGS_SEPARATOR) + 1;
+ vvalue = p_strdup(ctx->set_pool, value);
+
+ /* replace if it already exists */
+ items = array_get(link->array, &count);
+ for (i = 0; i < count; i += 2) {
+ if (strcmp(items[i], key) == 0) {
+ array_idx_set(link->array, i + 1, &vvalue);
+ return;
+ }
+ }
+
+ vkey = p_strdup(ctx->set_pool, key);
+ array_push_back(link->array, &vkey);
+ array_push_back(link->array, &vvalue);
+}
+
+int settings_parse_keyvalue(struct setting_parser_context *ctx,
+ const char *key, const char *value)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+ unsigned int n = 0;
+
+ ctx->error = NULL;
+ ctx->prev_info = NULL;
+
+ if (!settings_find_key_nth(ctx, key, &n, &def, &link)) {
+ ctx->error = p_strconcat(ctx->parser_pool,
+ "Unknown setting: ", key, NULL);
+ return 0;
+ }
+
+ do {
+ if (def == NULL) {
+ i_assert(link->info == &strlist_info);
+ settings_parse_strlist(ctx, link, key, value);
+ return 1;
+ }
+
+ if (settings_parse(ctx, link, def, key, value) < 0)
+ return -1;
+ /* there may be more instances of the setting */
+ } while (settings_find_key_nth(ctx, key, &n, &def, &link));
+ return 1;
+}
+
+bool settings_parse_is_valid_key(struct setting_parser_context *ctx,
+ const char *key)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+
+ return settings_find_key(ctx, key, &def, &link);
+}
+
+const char *settings_parse_unalias(struct setting_parser_context *ctx,
+ const char *key)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+
+ if (!settings_find_key(ctx, key, &def, &link))
+ return NULL;
+ if (def == NULL) {
+ /* strlist */
+ i_assert(link->info == &strlist_info);
+ return key;
+ }
+
+ while (def->type == SET_ALIAS) {
+ i_assert(def != link->info->defines);
+ def--;
+ }
+ return def->key;
+}
+
+const void *
+settings_parse_get_value(struct setting_parser_context *ctx,
+ const char *key, enum setting_type *type_r)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+
+ if (!settings_find_key(ctx, key, &def, &link))
+ return NULL;
+ if (link->set_struct == NULL || def == NULL)
+ return NULL;
+
+ *type_r = def->type;
+ return STRUCT_MEMBER_P(link->set_struct, def->offset);
+}
+
+bool settings_parse_is_changed(struct setting_parser_context *ctx,
+ const char *key)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+ const unsigned char *p;
+
+ if (!settings_find_key(ctx, key, &def, &link))
+ return FALSE;
+ if (link->change_struct == NULL || def == NULL)
+ return FALSE;
+
+ p = STRUCT_MEMBER_P(link->change_struct, def->offset);
+ return *p != 0;
+}
+
+int settings_parse_line(struct setting_parser_context *ctx, const char *line)
+{
+ const char *key, *value;
+ int ret;
+
+ key = line;
+ value = strchr(line, '=');
+ if (value == NULL) {
+ ctx->error = "Missing '='";
+ return -1;
+ }
+
+ if (key == value) {
+ ctx->error = "Missing key name ('=' at the beginning of line)";
+ return -1;
+ }
+
+ T_BEGIN {
+ key = t_strdup_until(key, value);
+ ret = settings_parse_keyvalue(ctx, key, value + 1);
+ } T_END;
+ return ret;
+}
+
+const struct setting_parser_info *
+settings_parse_get_prev_info(struct setting_parser_context *ctx)
+{
+ return ctx->prev_info;
+}
+
+static const char *settings_translate_lf(const char *value)
+{
+ char *dest, *p;
+
+ if (strchr(value, SETTING_STREAM_LF_CHAR[0]) == NULL)
+ return value;
+
+ dest = t_strdup_noconst(value);
+ for (p = dest; *p != '\0'; p++) {
+ if (*p == SETTING_STREAM_LF_CHAR[0])
+ *p = '\n';
+ }
+ return dest;
+}
+
+int settings_parse_stream(struct setting_parser_context *ctx,
+ struct istream *input)
+{
+ bool ignore_unknown_keys =
+ (ctx->flags & SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS) != 0;
+ const char *line;
+ int ret;
+
+ while ((line = i_stream_next_line(input)) != NULL) {
+ if (*line == '\0') {
+ /* empty line finishes it */
+ return 0;
+ }
+ ctx->linenum++;
+ if (ctx->linenum == 1 && str_begins(line, "ERROR ")) {
+ ctx->error = p_strdup(ctx->parser_pool, line + 6);
+ return -1;
+ }
+
+ T_BEGIN {
+ line = settings_translate_lf(line);
+ ret = settings_parse_line(ctx, line);
+ } T_END;
+
+ if (ret < 0 || (ret == 0 && !ignore_unknown_keys)) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Line %u: %s", ctx->linenum, ctx->error);
+ return -1;
+ }
+ }
+ return 1;
+}
+
+int settings_parse_stream_read(struct setting_parser_context *ctx,
+ struct istream *input)
+{
+ int ret;
+
+ do {
+ if ((ret = settings_parse_stream(ctx, input)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* empty line read */
+ return 0;
+ }
+ } while ((ret = i_stream_read(input)) > 0);
+
+ switch (ret) {
+ case -1:
+ if (ctx->error != NULL)
+ break;
+ if (input->stream_errno != 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "read(%s) failed: %s", i_stream_get_name(input),
+ i_stream_get_error(input));
+ } else if (input->v_offset == 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "read(%s) disconnected before receiving any data",
+ i_stream_get_name(input));
+ } else {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "read(%s) disconnected before receiving "
+ "end-of-settings line",
+ i_stream_get_name(input));
+ }
+ break;
+ case -2:
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Line %u: line too long",
+ ctx->linenum);
+ break;
+ case 0:
+ /* blocks */
+ return 1;
+ default:
+ i_unreached();
+ }
+ return -1;
+}
+
+int settings_parse_file(struct setting_parser_context *ctx,
+ const char *path, size_t max_line_length)
+{
+ struct istream *input;
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "open(%s) failed: %m", path);
+ return -1;
+ }
+
+ input = i_stream_create_fd_autoclose(&fd, max_line_length);
+ i_stream_set_name(input, path);
+ ret = settings_parse_stream_read(ctx, input);
+ i_stream_unref(&input);
+
+ return ret;
+}
+
+static int environ_cmp(char *const *s1, char *const *s2)
+{
+ return -strcmp(*s1, *s2);
+}
+
+int settings_parse_environ(struct setting_parser_context *ctx)
+{
+ char **environ = *env_get_environ_p();
+ ARRAY_TYPE(string) sorted_envs_arr;
+ const char *key, *value;
+ char *const *sorted_envs;
+ unsigned int i, count;
+ int ret = 0;
+
+ if (environ == NULL)
+ return 0;
+
+ /* sort the settings first. this is necessary for putenv()
+ implementations (e.g. valgrind) which change the order of strings
+ in environ[] */
+ i_array_init(&sorted_envs_arr, 128);
+ for (i = 0; environ[i] != NULL; i++)
+ array_push_back(&sorted_envs_arr, &environ[i]);
+ array_sort(&sorted_envs_arr, environ_cmp);
+ sorted_envs = array_get(&sorted_envs_arr, &count);
+
+ for (i = 0; i < count && ret == 0; i++) {
+ value = strchr(sorted_envs[i], '=');
+ if (value != NULL) T_BEGIN {
+ key = t_strdup_until(sorted_envs[i], value++);
+ key = t_str_lcase(key);
+ if (settings_parse_keyvalue(ctx, key, value) < 0) {
+ ctx->error = p_strdup_printf(ctx->parser_pool,
+ "Invalid setting %s: %s",
+ key, ctx->error);
+ ret = -1;
+ }
+ } T_END;
+ }
+ array_free(&sorted_envs_arr);
+ return ret;
+}
+
+int settings_parse_exec(struct setting_parser_context *ctx,
+ const char *bin_path, const char *config_path,
+ const char *service)
+{
+ struct istream *input;
+ pid_t pid;
+ int ret, fd[2], status;
+
+ if (pipe(fd) < 0) {
+ i_error("pipe() failed: %m");
+ return -1;
+ }
+
+ pid = fork();
+ if (pid == (pid_t)-1) {
+ i_error("fork() failed: %m");
+ i_close_fd(&fd[0]);
+ i_close_fd(&fd[1]);
+ return -1;
+ }
+ if (pid == 0) {
+ /* child */
+ static const char *argv[] = {
+ NULL,
+ "-c", NULL,
+ "-p", NULL,
+ NULL
+ };
+ argv[0] = bin_path;
+ argv[2] = config_path;
+ argv[4] = service;
+ i_close_fd(&fd[0]);
+ if (dup2(fd[1], STDOUT_FILENO) < 0)
+ i_fatal("dup2() failed: %m");
+
+ execv_const(argv[0], argv);
+ }
+ i_close_fd(&fd[1]);
+
+ input = i_stream_create_fd_autoclose(&fd[0], SIZE_MAX);
+ i_stream_set_name(input, bin_path);
+ ret = settings_parse_stream_read(ctx, input);
+ i_stream_destroy(&input);
+
+ if (waitpid(pid, &status, 0) < 0) {
+ i_error("waitpid() failed: %m");
+ ret = -1;
+ } else if (status != 0) {
+ i_error("%s returned failure: %d", bin_path, status);
+ ret = -1;
+ }
+ return ret;
+}
+
+static bool
+settings_check_dynamic(const struct setting_parser_info *info, pool_t pool,
+ void *set, const char **error_r)
+{
+ unsigned int i;
+
+ if (info->dynamic_parsers == NULL)
+ return TRUE;
+
+ for (i = 0; info->dynamic_parsers[i].name != NULL; i++) {
+ struct dynamic_settings_parser *dyn = &info->dynamic_parsers[i];
+
+ if (!settings_check(dyn->info, pool,
+ PTR_OFFSET(set, dyn->struct_offset),
+ error_r))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool settings_check(const struct setting_parser_info *info, pool_t pool,
+ void *set, const char **error_r)
+{
+ const struct setting_define *def;
+ const ARRAY_TYPE(void_array) *val;
+ void *const *children;
+ unsigned int i, count;
+ bool valid;
+
+ if (info->check_func != NULL) {
+ T_BEGIN {
+ valid = info->check_func(set, pool, error_r);
+ } T_END_PASS_STR_IF(!valid, error_r);
+ if (!valid)
+ return FALSE;
+ }
+
+ for (def = info->defines; def->key != NULL; def++) {
+ if (!SETTING_TYPE_IS_DEFLIST(def->type))
+ continue;
+
+ val = CONST_PTR_OFFSET(set, def->offset);
+ if (!array_is_created(val))
+ continue;
+
+ children = array_get(val, &count);
+ for (i = 0; i < count; i++) {
+ if (!settings_check(def->list_info, pool,
+ children[i], error_r))
+ return FALSE;
+ }
+ }
+ return settings_check_dynamic(info, pool, set, error_r);
+}
+
+bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool,
+ const char **error_r)
+{
+ unsigned int i;
+
+ for (i = 0; i < ctx->root_count; i++) {
+ if (!settings_check(ctx->roots[i].info, pool,
+ ctx->roots[i].set_struct, error_r))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void settings_parse_set_expanded(struct setting_parser_context *ctx,
+ bool is_expanded)
+{
+ ctx->str_vars_are_expanded = is_expanded;
+}
+
+void settings_parse_set_key_expanded(struct setting_parser_context *ctx,
+ pool_t pool, const char *key)
+{
+ const struct setting_define *def;
+ struct setting_link *link;
+ const char **val;
+
+ if (!settings_find_key(ctx, key, &def, &link))
+ return;
+ if (def == NULL) {
+ /* parent is strlist, no expansion needed */
+ i_assert(link->info == &strlist_info);
+ return;
+ }
+
+ val = PTR_OFFSET(link->set_struct, def->offset);
+ if (def->type == SET_STR_VARS && *val != NULL) {
+ i_assert(**val == SETTING_STRVAR_UNEXPANDED[0] ||
+ **val == SETTING_STRVAR_EXPANDED[0]);
+ *val = p_strconcat(pool, SETTING_STRVAR_EXPANDED,
+ *val + 1, NULL);
+ }
+}
+
+void settings_parse_set_keys_expanded(struct setting_parser_context *ctx,
+ pool_t pool, const char *const *keys)
+{
+ for (; *keys != NULL; keys++)
+ settings_parse_set_key_expanded(ctx, pool, *keys);
+}
+
+static int ATTR_NULL(3, 4, 5)
+settings_var_expand_info(const struct setting_parser_info *info, void *set,
+ pool_t pool,
+ const struct var_expand_table *table,
+ const struct var_expand_func_table *func_table,
+ void *func_context, string_t *str,
+ const char **error_r)
+{
+ const struct setting_define *def;
+ void *value, *const *children;
+ const char *error;
+ unsigned int i, count;
+ int ret, final_ret = 1;
+
+ for (def = info->defines; def->key != NULL; def++) {
+ value = PTR_OFFSET(set, def->offset);
+ switch (def->type) {
+ case SET_BOOL:
+ case SET_UINT:
+ case SET_UINT_OCT:
+ case SET_TIME:
+ case SET_TIME_MSECS:
+ case SET_SIZE:
+ case SET_IN_PORT:
+ case SET_STR:
+ case SET_ENUM:
+ case SET_STRLIST:
+ case SET_ALIAS:
+ break;
+ case SET_STR_VARS: {
+ const char **val = value;
+
+ if (*val == NULL)
+ break;
+
+ if (table == NULL) {
+ i_assert(**val == SETTING_STRVAR_EXPANDED[0] ||
+ **val == SETTING_STRVAR_UNEXPANDED[0]);
+ *val += 1;
+ } else if (**val == SETTING_STRVAR_UNEXPANDED[0]) {
+ str_truncate(str, 0);
+ ret = var_expand_with_funcs(str, *val + 1, table,
+ func_table, func_context,
+ &error);
+ if (final_ret > ret) {
+ final_ret = ret;
+ *error_r = t_strdup_printf(
+ "%s: %s", def->key, error);
+ }
+ *val = p_strdup(pool, str_c(str));
+ } else {
+ i_assert(**val == SETTING_STRVAR_EXPANDED[0]);
+ *val += 1;
+ }
+ break;
+ }
+ case SET_DEFLIST:
+ case SET_DEFLIST_UNIQUE: {
+ const ARRAY_TYPE(void_array) *val = value;
+
+ if (!array_is_created(val))
+ break;
+
+ children = array_get(val, &count);
+ for (i = 0; i < count; i++) {
+ ret = settings_var_expand_info(def->list_info,
+ children[i], pool, table, func_table,
+ func_context, str, &error);
+ if (final_ret > ret) {
+ final_ret = ret;
+ *error_r = error;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (final_ret <= 0)
+ return final_ret;
+
+ if (info->expand_check_func != NULL) {
+ if (!info->expand_check_func(set, pool, error_r))
+ return -1;
+ }
+ if (info->dynamic_parsers != NULL) {
+ for (i = 0; info->dynamic_parsers[i].name != NULL; i++) {
+ struct dynamic_settings_parser *dyn = &info->dynamic_parsers[i];
+ const struct setting_parser_info *dinfo = dyn->info;
+ void *dset = PTR_OFFSET(set, dyn->struct_offset);
+
+ if (dinfo->expand_check_func != NULL) {
+ if (!dinfo->expand_check_func(dset, pool, error_r))
+ return -1;
+ }
+ }
+ }
+
+ return final_ret;
+}
+
+int settings_var_expand(const struct setting_parser_info *info,
+ void *set, pool_t pool,
+ const struct var_expand_table *table,
+ const char **error_r)
+{
+ return settings_var_expand_with_funcs(info, set, pool, table,
+ NULL, NULL, error_r);
+}
+
+int settings_var_expand_with_funcs(const struct setting_parser_info *info,
+ void *set, pool_t pool,
+ const struct var_expand_table *table,
+ const struct var_expand_func_table *func_table,
+ void *func_context, const char **error_r)
+{
+ int ret;
+
+ T_BEGIN {
+ string_t *str = t_str_new(256);
+
+ ret = settings_var_expand_info(info, set, pool, table,
+ func_table, func_context, str,
+ error_r);
+ } T_END_PASS_STR_IF(ret <= 0, error_r);
+ return ret;
+}
+
+void settings_parse_var_skip(struct setting_parser_context *ctx)
+{
+ unsigned int i;
+ const char *error;
+
+ for (i = 0; i < ctx->root_count; i++) {
+ (void)settings_var_expand_info(ctx->roots[i].info,
+ ctx->roots[i].set_struct,
+ NULL, NULL, NULL, NULL, NULL,
+ &error);
+ }
+}
+
+bool settings_vars_have_key(const struct setting_parser_info *info, void *set,
+ char var_key, const char *long_var_key,
+ const char **key_r, const char **value_r)
+{
+ const struct setting_define *def;
+ const void *value;
+ void *const *children;
+ unsigned int i, count;
+
+ for (def = info->defines; def->key != NULL; def++) {
+ value = CONST_PTR_OFFSET(set, def->offset);
+ switch (def->type) {
+ case SET_BOOL:
+ case SET_UINT:
+ case SET_UINT_OCT:
+ case SET_TIME:
+ case SET_TIME_MSECS:
+ case SET_SIZE:
+ case SET_IN_PORT:
+ case SET_STR:
+ case SET_ENUM:
+ case SET_STRLIST:
+ case SET_ALIAS:
+ break;
+ case SET_STR_VARS: {
+ const char *const *val = value;
+
+ if (*val == NULL)
+ break;
+
+ if (**val == SETTING_STRVAR_UNEXPANDED[0]) {
+ if (var_has_key(*val + 1, var_key,
+ long_var_key)) {
+ *key_r = def->key;
+ *value_r = *val + 1;
+ return TRUE;
+ }
+ } else {
+ i_assert(**val == SETTING_STRVAR_EXPANDED[0]);
+ }
+ break;
+ }
+ case SET_DEFLIST:
+ case SET_DEFLIST_UNIQUE: {
+ const ARRAY_TYPE(void_array) *val = value;
+
+ if (!array_is_created(val))
+ break;
+
+ children = array_get(val, &count);
+ for (i = 0; i < count; i++) {
+ if (settings_vars_have_key(def->list_info,
+ children[i], var_key,
+ long_var_key,
+ key_r, value_r))
+ return TRUE;
+ }
+ break;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static void settings_set_parent(const struct setting_parser_info *info,
+ void *child, void *parent)
+{
+ void **ptr;
+
+ if (info->parent_offset == SIZE_MAX)
+ return;
+
+ ptr = PTR_OFFSET(child, info->parent_offset);
+ *ptr = parent;
+}
+
+static bool
+setting_copy(enum setting_type type, const void *src, void *dest, pool_t pool,
+ bool keep_values)
+{
+ switch (type) {
+ case SET_BOOL: {
+ const bool *src_bool = src;
+ bool *dest_bool = dest;
+
+ *dest_bool = *src_bool;
+ break;
+ }
+ case SET_UINT:
+ case SET_UINT_OCT:
+ case SET_TIME:
+ case SET_TIME_MSECS: {
+ const unsigned int *src_uint = src;
+ unsigned int *dest_uint = dest;
+
+ *dest_uint = *src_uint;
+ break;
+ }
+ case SET_SIZE: {
+ const uoff_t *src_size = src;
+ uoff_t *dest_size = dest;
+
+ *dest_size = *src_size;
+ break;
+ }
+ case SET_IN_PORT: {
+ const in_port_t *src_size = src;
+ in_port_t *dest_size = dest;
+
+ *dest_size = *src_size;
+ break;
+ }
+ case SET_STR_VARS:
+ case SET_STR:
+ case SET_ENUM: {
+ const char *const *src_str = src;
+ const char **dest_str = dest;
+
+ if (keep_values)
+ *dest_str = *src_str;
+ else
+ *dest_str = p_strdup(pool, *src_str);
+ break;
+ }
+ case SET_DEFLIST:
+ case SET_DEFLIST_UNIQUE:
+ return FALSE;
+ case SET_STRLIST: {
+ const ARRAY_TYPE(const_string) *src_arr = src;
+ ARRAY_TYPE(const_string) *dest_arr = dest;
+ const char *const *strings, *const *dest_strings, *dup;
+ unsigned int i, j, count, dest_count;
+
+ if (!array_is_created(src_arr))
+ break;
+
+ strings = array_get(src_arr, &count);
+ i_assert(count % 2 == 0);
+ if (!array_is_created(dest_arr))
+ p_array_init(dest_arr, pool, count);
+ dest_count = array_count(dest_arr);
+ i_assert(dest_count % 2 == 0);
+ for (i = 0; i < count; i += 2) {
+ if (dest_count > 0) {
+ dest_strings = array_front(dest_arr);
+ for (j = 0; j < dest_count; j += 2) {
+ if (strcmp(strings[i], dest_strings[j]) == 0)
+ break;
+ }
+ if (j < dest_count)
+ continue;
+ }
+ dup = keep_values ? strings[i] : p_strdup(pool, strings[i]);
+ array_push_back(dest_arr, &dup);
+ dup = keep_values ? strings[i+1] : p_strdup(pool, strings[i+1]);
+ array_push_back(dest_arr, &dup);
+ }
+ break;
+ }
+ case SET_ALIAS:
+ break;
+ }
+ return TRUE;
+}
+
+static void *settings_dup_full(const struct setting_parser_info *info,
+ const void *set, pool_t pool, bool keep_values)
+{
+ const struct setting_define *def;
+ const void *src;
+ void *dest_set, *dest, *const *children;
+ unsigned int i, count;
+
+ if (info->struct_size == 0)
+ return NULL;
+
+ /* don't just copy everything from set to dest_set. it may contain
+ some non-setting fields allocated from the original pool. */
+ dest_set = p_malloc(pool, info->struct_size);
+ for (def = info->defines; def->key != NULL; def++) {
+ src = CONST_PTR_OFFSET(set, def->offset);
+ dest = PTR_OFFSET(dest_set, def->offset);
+
+ if (!setting_copy(def->type, src, dest, pool, keep_values)) {
+ const ARRAY_TYPE(void_array) *src_arr = src;
+ ARRAY_TYPE(void_array) *dest_arr = dest;
+ void *child_set;
+
+ if (!array_is_created(src_arr))
+ continue;
+
+ children = array_get(src_arr, &count);
+ p_array_init(dest_arr, pool, count);
+ for (i = 0; i < count; i++) {
+ child_set = settings_dup_full(def->list_info,
+ children[i], pool,
+ keep_values);
+ array_push_back(dest_arr, &child_set);
+ settings_set_parent(def->list_info, child_set,
+ dest_set);
+ }
+ }
+ }
+ return dest_set;
+}
+
+void *settings_dup(const struct setting_parser_info *info,
+ const void *set, pool_t pool)
+{
+ return settings_dup_full(info, set, pool, FALSE);
+}
+
+void *settings_dup_with_pointers(const struct setting_parser_info *info,
+ const void *set, pool_t pool)
+{
+ return settings_dup_full(info, set, pool, TRUE);
+}
+
+static void *
+settings_changes_dup(const struct setting_parser_info *info,
+ const void *change_set, pool_t pool)
+{
+ const struct setting_define *def;
+ const void *src;
+ void *dest_set, *dest, *const *children;
+ unsigned int i, count;
+
+ if (change_set == NULL || info->struct_size == 0)
+ return NULL;
+
+ dest_set = p_malloc(pool, info->struct_size);
+ for (def = info->defines; def->key != NULL; def++) {
+ src = CONST_PTR_OFFSET(change_set, def->offset);
+ dest = PTR_OFFSET(dest_set, def->offset);
+
+ switch (def->type) {
+ case SET_BOOL:
+ case SET_UINT:
+ case SET_UINT_OCT:
+ case SET_TIME:
+ case SET_TIME_MSECS:
+ case SET_SIZE:
+ case SET_IN_PORT:
+ case SET_STR_VARS:
+ case SET_STR:
+ case SET_ENUM:
+ case SET_STRLIST:
+ *((char *)dest) = *((const char *)src);
+ break;
+ case SET_DEFLIST:
+ case SET_DEFLIST_UNIQUE: {
+ const ARRAY_TYPE(void_array) *src_arr = src;
+ ARRAY_TYPE(void_array) *dest_arr = dest;
+ void *child_set;
+
+ if (!array_is_created(src_arr))
+ break;
+
+ children = array_get(src_arr, &count);
+ p_array_init(dest_arr, pool, count);
+ for (i = 0; i < count; i++) {
+ child_set = settings_changes_dup(def->list_info,
+ children[i],
+ pool);
+ array_push_back(dest_arr, &child_set);
+ }
+ break;
+ }
+ case SET_ALIAS:
+ break;
+ }
+ }
+ return dest_set;
+}
+
+static void
+info_update_real(pool_t pool, struct setting_parser_info *parent,
+ const struct dynamic_settings_parser *parsers)
+{
+ /* @UNSAFE */
+ ARRAY(struct setting_define) defines;
+ ARRAY_TYPE(dynamic_settings_parser) dynamic_parsers;
+ struct dynamic_settings_parser new_parser;
+ const struct setting_define *cur_defines;
+ struct setting_define *new_defines, new_define;
+ void *parent_defaults;
+ unsigned int i, j;
+ size_t offset, new_struct_size;
+
+ t_array_init(&defines, 128);
+ /* add existing defines */
+ for (j = 0; parent->defines[j].key != NULL; j++)
+ array_push_back(&defines, &parent->defines[j]);
+ new_struct_size = MEM_ALIGN(parent->struct_size);
+
+ /* add new dynamic defines */
+ for (i = 0; parsers[i].name != NULL; i++) {
+ i_assert(parsers[i].info->parent == parent);
+ cur_defines = parsers[i].info->defines;
+ for (j = 0; cur_defines[j].key != NULL; j++) {
+ new_define = cur_defines[j];
+ new_define.offset += new_struct_size;
+ array_push_back(&defines, &new_define);
+ }
+ new_struct_size += MEM_ALIGN(parsers[i].info->struct_size);
+ }
+ new_defines = p_new(pool, struct setting_define,
+ array_count(&defines) + 1);
+ memcpy(new_defines, array_front(&defines),
+ sizeof(*parent->defines) * array_count(&defines));
+ parent->defines = new_defines;
+
+ /* update defaults */
+ parent_defaults = p_malloc(pool, new_struct_size);
+ memcpy(parent_defaults, parent->defaults, parent->struct_size);
+ offset = MEM_ALIGN(parent->struct_size);
+ for (i = 0; parsers[i].name != NULL; i++) {
+ memcpy(PTR_OFFSET(parent_defaults, offset),
+ parsers[i].info->defaults, parsers[i].info->struct_size);
+ offset += MEM_ALIGN(parsers[i].info->struct_size);
+ }
+ parent->defaults = parent_defaults;
+
+ /* update dynamic parsers list */
+ t_array_init(&dynamic_parsers, 32);
+ if (parent->dynamic_parsers != NULL) {
+ for (i = 0; parent->dynamic_parsers[i].name != NULL; i++) {
+ array_push_back(&dynamic_parsers,
+ &parent->dynamic_parsers[i]);
+ }
+ }
+ offset = MEM_ALIGN(parent->struct_size);
+ for (i = 0; parsers[i].name != NULL; i++) {
+ new_parser = parsers[i];
+ new_parser.name = p_strdup(pool, new_parser.name);
+ new_parser.struct_offset = offset;
+ array_push_back(&dynamic_parsers, &new_parser);
+ offset += MEM_ALIGN(parsers[i].info->struct_size);
+ }
+ parent->dynamic_parsers =
+ p_new(pool, struct dynamic_settings_parser,
+ array_count(&dynamic_parsers) + 1);
+ memcpy(parent->dynamic_parsers, array_front(&dynamic_parsers),
+ sizeof(*parent->dynamic_parsers) *
+ array_count(&dynamic_parsers));
+ parent->struct_size = new_struct_size;
+}
+
+void settings_parser_info_update(pool_t pool,
+ struct setting_parser_info *parent,
+ const struct dynamic_settings_parser *parsers)
+{
+ if (parsers[0].name != NULL) T_BEGIN {
+ info_update_real(pool, parent, parsers);
+ } T_END;
+}
+
+static void
+settings_parser_update_children_parent(struct setting_parser_info *parent,
+ pool_t pool)
+{
+ struct setting_define *new_defs;
+ struct setting_parser_info *new_info;
+ unsigned int i, count;
+
+ for (count = 0; parent->defines[count].key != NULL; count++) ;
+
+ new_defs = p_new(pool, struct setting_define, count + 1);
+ memcpy(new_defs, parent->defines, sizeof(*new_defs) * count);
+ parent->defines = new_defs;
+
+ for (i = 0; i < count; i++) {
+ if (new_defs[i].list_info == NULL ||
+ new_defs[i].list_info->parent == NULL)
+ continue;
+
+ new_info = p_new(pool, struct setting_parser_info, 1);
+ *new_info = *new_defs[i].list_info;
+ new_info->parent = parent;
+ new_defs[i].list_info = new_info;
+ }
+}
+
+void settings_parser_dyn_update(pool_t pool,
+ const struct setting_parser_info *const **_roots,
+ const struct dynamic_settings_parser *dyn_parsers)
+{
+ const struct setting_parser_info *const *roots = *_roots;
+ const struct setting_parser_info *old_parent, **new_roots;
+ struct setting_parser_info *new_parent, *new_info;
+ struct dynamic_settings_parser *new_dyn_parsers;
+ unsigned int i, count;
+
+ /* settings_parser_info_update() modifies the parent structure.
+ since we may be using the same structure later, we want it to be
+ in its original state, so we'll have to copy all structures. */
+ old_parent = dyn_parsers[0].info->parent;
+ new_parent = p_new(pool, struct setting_parser_info, 1);
+ *new_parent = *old_parent;
+ settings_parser_update_children_parent(new_parent, pool);
+
+ /* update root */
+ for (count = 0; roots[count] != NULL; count++) ;
+ new_roots = p_new(pool, const struct setting_parser_info *, count + 1);
+ for (i = 0; i < count; i++) {
+ if (roots[i] == old_parent)
+ new_roots[i] = new_parent;
+ else
+ new_roots[i] = roots[i];
+ }
+ *_roots = new_roots;
+
+ /* update parent in dyn_parsers */
+ for (count = 0; dyn_parsers[count].name != NULL; count++) ;
+ new_dyn_parsers = p_new(pool, struct dynamic_settings_parser, count + 1);
+ for (i = 0; i < count; i++) {
+ new_dyn_parsers[i] = dyn_parsers[i];
+
+ new_info = p_new(pool, struct setting_parser_info, 1);
+ *new_info = *dyn_parsers[i].info;
+ new_info->parent = new_parent;
+ new_dyn_parsers[i].info = new_info;
+ }
+
+ settings_parser_info_update(pool, new_parent, new_dyn_parsers);
+}
+
+const void *settings_find_dynamic(const struct setting_parser_info *info,
+ const void *base_set, const char *name)
+{
+ unsigned int i;
+
+ if (info->dynamic_parsers == NULL)
+ return NULL;
+
+ for (i = 0; info->dynamic_parsers[i].name != NULL; i++) {
+ if (strcmp(info->dynamic_parsers[i].name, name) == 0) {
+ return CONST_PTR_OFFSET(base_set,
+ info->dynamic_parsers[i].struct_offset);
+ }
+ }
+ return NULL;
+}
+
+static struct setting_link *
+settings_link_get_new(struct setting_parser_context *new_ctx,
+ HASH_TABLE_TYPE(setting_link) links,
+ struct setting_link *old_link)
+{
+ struct setting_link *new_link;
+ void *const *old_sets, **new_sets;
+ unsigned int i, count, count2;
+ size_t diff;
+
+ new_link = hash_table_lookup(links, old_link);
+ if (new_link != NULL)
+ return new_link;
+
+ i_assert(old_link->parent != NULL);
+ i_assert(old_link->array != NULL);
+
+ new_link = p_new(new_ctx->parser_pool, struct setting_link, 1);
+ new_link->info = old_link->info;
+ new_link->parent = settings_link_get_new(new_ctx, links,
+ old_link->parent);
+
+ /* find the array from parent struct */
+ diff = (char *)old_link->array - (char *)old_link->parent->set_struct;
+ i_assert(diff + sizeof(*old_link->array) <= old_link->parent->info->struct_size);
+ new_link->array = PTR_OFFSET(new_link->parent->set_struct, diff);
+
+ if (old_link->set_struct != NULL) {
+ /* find our struct from array */
+ old_sets = array_get(old_link->array, &count);
+ new_sets = array_get_modifiable(new_link->array, &count2);
+ i_assert(count == count2);
+ for (i = 0; i < count; i++) {
+ if (old_sets[i] == old_link->set_struct) {
+ new_link->set_struct = new_sets[i];
+ break;
+ }
+ }
+ i_assert(i < count);
+ }
+ i_assert(hash_table_lookup(links, old_link) == NULL);
+ hash_table_insert(links, old_link, new_link);
+ return new_link;
+}
+
+struct setting_parser_context *
+settings_parser_dup(const struct setting_parser_context *old_ctx,
+ pool_t new_pool)
+{
+ struct setting_parser_context *new_ctx;
+ struct hash_iterate_context *iter;
+ HASH_TABLE_TYPE(setting_link) links;
+ struct setting_link *new_link, *value;
+ char *key;
+ unsigned int i;
+ pool_t parser_pool;
+ bool keep_values;
+
+ /* if source and destination pools are the same, there's no need to
+ duplicate values */
+ keep_values = new_pool == old_ctx->set_pool;
+
+ pool_ref(new_pool);
+ parser_pool = pool_alloconly_create(MEMPOOL_GROWING"dup settings parser",
+ 1024);
+ new_ctx = p_new(parser_pool, struct setting_parser_context, 1);
+ new_ctx->set_pool = new_pool;
+ new_ctx->parser_pool = parser_pool;
+ new_ctx->flags = old_ctx->flags;
+ new_ctx->str_vars_are_expanded = old_ctx->str_vars_are_expanded;
+ new_ctx->linenum = old_ctx->linenum;
+ new_ctx->error = p_strdup(new_ctx->parser_pool, old_ctx->error);
+ new_ctx->prev_info = old_ctx->prev_info;
+
+ hash_table_create_direct(&links, new_ctx->parser_pool, 0);
+
+ new_ctx->root_count = old_ctx->root_count;
+ new_ctx->roots = p_new(new_ctx->parser_pool, struct setting_link,
+ new_ctx->root_count);
+ for (i = 0; i < new_ctx->root_count; i++) {
+ i_assert(old_ctx->roots[i].parent == NULL);
+ i_assert(old_ctx->roots[i].array == NULL);
+
+ new_ctx->roots[i].info = old_ctx->roots[i].info;
+ new_ctx->roots[i].set_struct =
+ settings_dup_full(old_ctx->roots[i].info,
+ old_ctx->roots[i].set_struct,
+ new_ctx->set_pool, keep_values);
+ new_ctx->roots[i].change_struct =
+ settings_changes_dup(old_ctx->roots[i].info,
+ old_ctx->roots[i].change_struct,
+ new_ctx->set_pool);
+ hash_table_insert(links, &old_ctx->roots[i],
+ &new_ctx->roots[i]);
+ }
+
+ hash_table_create(&new_ctx->links, new_ctx->parser_pool, 0,
+ strcase_hash, strcasecmp);
+
+ iter = hash_table_iterate_init(old_ctx->links);
+ while (hash_table_iterate(iter, old_ctx->links, &key, &value)) {
+ new_link = settings_link_get_new(new_ctx, links, value);
+ key = p_strdup(new_ctx->parser_pool, key);
+ hash_table_insert(new_ctx->links, key, new_link);
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&links);
+ return new_ctx;
+}
+
+static void *
+settings_changes_init(const struct setting_parser_info *info,
+ const void *change_set, pool_t pool)
+{
+ const struct setting_define *def;
+ const ARRAY_TYPE(void_array) *src_arr;
+ ARRAY_TYPE(void_array) *dest_arr;
+ void *dest_set, *set, *const *children;
+ unsigned int i, count;
+
+ if (info->struct_size == 0)
+ return NULL;
+
+ dest_set = p_malloc(pool, info->struct_size);
+ for (def = info->defines; def->key != NULL; def++) {
+ if (!SETTING_TYPE_IS_DEFLIST(def->type))
+ continue;
+
+ src_arr = CONST_PTR_OFFSET(change_set, def->offset);
+ dest_arr = PTR_OFFSET(dest_set, def->offset);
+
+ if (array_is_created(src_arr)) {
+ children = array_get(src_arr, &count);
+ i_assert(!array_is_created(dest_arr));
+ p_array_init(dest_arr, pool, count);
+ for (i = 0; i < count; i++) {
+ set = settings_changes_init(def->list_info,
+ children[i], pool);
+ array_push_back(dest_arr, &set);
+ }
+ }
+ }
+ return dest_set;
+}
+
+static void settings_copy_deflist(const struct setting_define *def,
+ const struct setting_link *src_link,
+ struct setting_link *dest_link,
+ pool_t pool)
+{
+ const ARRAY_TYPE(void_array) *src_arr;
+ ARRAY_TYPE(void_array) *dest_arr;
+ void *const *children, *child_set;
+ unsigned int i, count;
+
+ src_arr = CONST_PTR_OFFSET(src_link->set_struct, def->offset);
+ dest_arr = PTR_OFFSET(dest_link->set_struct, def->offset);
+
+ if (!array_is_created(src_arr))
+ return;
+
+ children = array_get(src_arr, &count);
+ if (!array_is_created(dest_arr))
+ p_array_init(dest_arr, pool, count);
+ for (i = 0; i < count; i++) {
+ child_set = settings_dup(def->list_info, children[i], pool);
+ array_push_back(dest_arr, &child_set);
+ settings_set_parent(def->list_info, child_set,
+ dest_link->set_struct);
+ }
+
+ /* copy changes */
+ dest_arr = PTR_OFFSET(dest_link->change_struct, def->offset);
+ if (!array_is_created(dest_arr))
+ p_array_init(dest_arr, pool, count);
+ for (i = 0; i < count; i++) {
+ child_set = settings_changes_init(def->list_info,
+ children[i], pool);
+ array_push_back(dest_arr, &child_set);
+ }
+}
+
+static int
+settings_copy_deflist_unique(const struct setting_define *def,
+ const struct setting_link *src_link,
+ struct setting_link *dest_link,
+ pool_t pool, const char **conflict_key_r)
+{
+ struct setting_link child_dest_link, child_src_link;
+ const ARRAY_TYPE(void_array) *src_arr, *src_carr;
+ ARRAY_TYPE(void_array) *dest_arr, *dest_carr;
+ void *const *src_children, *const *src_cchildren;
+ void *const *dest_children, *const *dest_cchildren, *child_set;
+ const char *const *src_namep, *const *dest_namep;
+ unsigned int i, j, src_count, dest_count, ccount;
+ unsigned int type_offset;
+
+ i_assert(def->list_info->type_offset != SIZE_MAX);
+
+ src_arr = CONST_PTR_OFFSET(src_link->set_struct, def->offset);
+ src_carr = CONST_PTR_OFFSET(src_link->change_struct, def->offset);
+ dest_arr = PTR_OFFSET(dest_link->set_struct, def->offset);
+ dest_carr = PTR_OFFSET(dest_link->change_struct, def->offset);
+
+ if (!array_is_created(src_arr))
+ return 0;
+ type_offset = def->list_info->type_offset;
+
+ i_zero(&child_dest_link);
+ i_zero(&child_src_link);
+
+ child_dest_link.info = child_src_link.info = def->list_info;
+
+ src_children = array_get(src_arr, &src_count);
+ src_cchildren = array_get(src_carr, &ccount);
+ i_assert(src_count == ccount);
+ if (!array_is_created(dest_arr)) {
+ p_array_init(dest_arr, pool, src_count);
+ p_array_init(dest_carr, pool, src_count);
+ }
+ for (i = 0; i < src_count; i++) {
+ src_namep = CONST_PTR_OFFSET(src_children[i], type_offset);
+ dest_children = array_get(dest_arr, &dest_count);
+ dest_cchildren = array_get(dest_carr, &ccount);
+ i_assert(dest_count == ccount);
+ for (j = 0; j < dest_count; j++) {
+ dest_namep = CONST_PTR_OFFSET(dest_children[j],
+ type_offset);
+ if (strcmp(*src_namep, *dest_namep) == 0)
+ break;
+ }
+
+ if (j < dest_count && **src_namep != '\0') {
+ /* merge */
+ child_src_link.set_struct = src_children[i];
+ child_src_link.change_struct = src_cchildren[i];
+ child_dest_link.set_struct = dest_children[j];
+ child_dest_link.change_struct = dest_cchildren[j];
+ if (settings_apply(&child_dest_link, &child_src_link,
+ pool, conflict_key_r) < 0)
+ return -1;
+ } else {
+ /* append */
+ child_set = settings_dup(def->list_info,
+ src_children[i], pool);
+ array_push_back(dest_arr, &child_set);
+ settings_set_parent(def->list_info, child_set,
+ dest_link->set_struct);
+
+ child_set = settings_changes_init(def->list_info,
+ src_cchildren[i],
+ pool);
+ array_push_back(dest_carr, &child_set);
+ }
+ }
+ return 0;
+}
+
+static int
+settings_apply(struct setting_link *dest_link,
+ const struct setting_link *src_link,
+ pool_t pool, const char **conflict_key_r)
+{
+ const struct setting_define *def;
+ const void *src, *csrc;
+ void *dest, *cdest;
+
+ for (def = dest_link->info->defines; def->key != NULL; def++) {
+ csrc = CONST_PTR_OFFSET(src_link->change_struct, def->offset);
+ cdest = PTR_OFFSET(dest_link->change_struct, def->offset);
+
+ if (def->type == SET_DEFLIST || def->type == SET_STRLIST) {
+ /* just add the new values */
+ } else if (def->type == SET_DEFLIST_UNIQUE) {
+ /* merge sections */
+ } else if (*((const char *)csrc) == 0) {
+ /* unchanged */
+ continue;
+ } else if (def->type == SET_ALIAS) {
+ /* ignore aliases */
+ continue;
+ } else if (*((const char *)cdest) != 0) {
+ /* conflict */
+ if (conflict_key_r != NULL) {
+ *conflict_key_r = def->key;
+ return -1;
+ }
+ continue;
+ } else {
+ *((char *)cdest) = 1;
+ }
+
+ /* found a changed setting */
+ src = CONST_PTR_OFFSET(src_link->set_struct, def->offset);
+ dest = PTR_OFFSET(dest_link->set_struct, def->offset);
+
+ if (setting_copy(def->type, src, dest, pool, FALSE)) {
+ /* non-list */
+ } else if (def->type == SET_DEFLIST) {
+ settings_copy_deflist(def, src_link, dest_link, pool);
+ } else {
+ i_assert(def->type == SET_DEFLIST_UNIQUE);
+ if (settings_copy_deflist_unique(def, src_link,
+ dest_link, pool,
+ conflict_key_r) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int settings_parser_apply_changes(struct setting_parser_context *dest,
+ const struct setting_parser_context *src,
+ pool_t pool, const char **conflict_key_r)
+{
+ unsigned int i;
+
+ i_assert(src->root_count == dest->root_count);
+ for (i = 0; i < dest->root_count; i++) {
+ i_assert(src->roots[i].info == dest->roots[i].info);
+ if (settings_apply(&dest->roots[i], &src->roots[i], pool,
+ conflict_key_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+const char *settings_section_escape(const char *name)
+{
+#define CHAR_NEED_ESCAPE(c) \
+ ((c) == '=' || (c) == SETTINGS_SEPARATOR || (c) == '\\' || (c) == ' ' || (c) == ',')
+ string_t *str;
+ unsigned int i;
+
+ for (i = 0; name[i] != '\0'; i++) {
+ if (CHAR_NEED_ESCAPE(name[i]))
+ break;
+ }
+ if (name[i] == '\0')
+ return name;
+
+ str = t_str_new(i + strlen(name+i) + 8);
+ str_append_data(str, name, i);
+ for (; name[i] != '\0'; i++) {
+ switch (name[i]) {
+ case '=':
+ str_append(str, "\\e");
+ break;
+ case SETTINGS_SEPARATOR:
+ str_append(str, "\\s");
+ break;
+ case '\\':
+ str_append(str, "\\\\");
+ break;
+ case ' ':
+ str_append(str, "\\_");
+ break;
+ case ',':
+ str_append(str, "\\+");
+ break;
+ default:
+ str_append_c(str, name[i]);
+ break;
+ }
+ }
+ return str_c(str);
+}
+
diff --git a/src/lib-settings/settings-parser.h b/src/lib-settings/settings-parser.h
new file mode 100644
index 0000000..d7ffcb5
--- /dev/null
+++ b/src/lib-settings/settings-parser.h
@@ -0,0 +1,281 @@
+#ifndef SETTINGS_PARSER_H
+#define SETTINGS_PARSER_H
+
+struct var_expand_table;
+struct var_expand_func_table;
+
+#define SETTINGS_SEPARATOR '/'
+#define SETTINGS_SEPARATOR_S "/"
+
+/* STR_VARS pointer begins with either of these initially. Before actually
+ using the variables all variables in all unexpanded strings need to be
+ expanded. Afterwards the string pointers should be increased to skip
+ the initial '1' so it'll be easy to use them. */
+#define SETTING_STRVAR_UNEXPANDED "0"
+#define SETTING_STRVAR_EXPANDED "1"
+
+/* When parsing streams, this character is translated to LF. */
+#define SETTING_STREAM_LF_CHAR "\003"
+
+enum setting_type {
+ SET_BOOL,
+ SET_UINT,
+ SET_UINT_OCT,
+ SET_TIME,
+ SET_TIME_MSECS,
+ SET_SIZE,
+ SET_IN_PORT, /* internet port */
+ SET_STR,
+ SET_STR_VARS, /* string with %variables */
+ SET_ENUM,
+ SET_DEFLIST, /* of type array_t */
+ SET_DEFLIST_UNIQUE,
+ SET_STRLIST, /* of type ARRAY_TYPE(const_string) */
+ SET_ALIAS /* alias name for above setting definition */
+};
+enum setting_flags {
+ SET_FLAG_HIDDEN = BIT(0),
+};
+#define SETTING_TYPE_IS_DEFLIST(type) \
+ ((type) == SET_DEFLIST || (type) == SET_DEFLIST_UNIQUE)
+
+#define SETTING_DEFINE_LIST_END { 0, 0, NULL, 0, NULL }
+
+struct setting_define {
+ enum setting_type type;
+ enum setting_flags flags;
+ const char *key;
+
+ size_t offset;
+ const struct setting_parser_info *list_info;
+};
+
+#define SETTING_DEFINE_STRUCT_TYPE(_enum_type, _flags, _c_type, _key, _name, _struct_name) \
+ { .type = (_enum_type) + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
+ ((_struct_name *)0)->_name, _c_type), \
+ .flags = _flags, .key = _key, \
+ .offset = offsetof(_struct_name, _name) }
+
+#define SETTING_DEFINE_STRUCT_BOOL(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_BOOL, 0, bool, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_UINT(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_UINT, 0, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_UINT_OCT(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_UINT_OCT, 0, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_TIME(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_TIME, 0, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_TIME_MSECS(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_TIME_MSECS, 0, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_SIZE(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_SIZE, 0, uoff_t, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_IN_PORT(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_IN_PORT, 0, in_port_t, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_STR(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_STR, 0, const char *, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_STR_VARS(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_STR_VARS, 0, const char *, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_ENUM(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_ENUM, 0, const char *, key, name, struct_name)
+
+#define SETTING_DEFINE_STRUCT_BOOL_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_BOOL, SET_FLAG_HIDDEN, bool, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_UINT_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_UINT, SET_FLAG_HIDDEN, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_UINT_OCT_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_UINT_OCT, SET_FLAG_HIDDEN, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_TIME_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_TIME, SET_FLAG_HIDDEN, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_TIME_MSECS_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_TIME_MSECS, SET_FLAG_HIDDEN, unsigned int, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_SIZE_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_SIZE, SET_FLAG_HIDDEN, uoff_t, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_IN_PORT_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_IN_PORT, SET_FLAG_HIDDEN, in_port_t, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_STR_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_STR, SET_FLAG_HIDDEN, const char *, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_STR_VARS_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_STR_VARS, SET_FLAG_HIDDEN, const char *, key, name, struct_name)
+#define SETTING_DEFINE_STRUCT_ENUM_HIDDEN(key, name, struct_name) \
+ SETTING_DEFINE_STRUCT_TYPE(SET_ENUM, SET_FLAG_HIDDEN, const char *, key, name, struct_name)
+
+struct setting_parser_info {
+ const char *module_name;
+ const struct setting_define *defines;
+ const void *defaults;
+
+ size_t type_offset;
+ size_t struct_size;
+
+ size_t parent_offset;
+ const struct setting_parser_info *parent;
+
+ bool (*check_func)(void *set, pool_t pool, const char **error_r);
+ bool (*expand_check_func)(void *set, pool_t pool, const char **error_r);
+ const struct setting_parser_info *const *dependencies;
+ struct dynamic_settings_parser *dynamic_parsers;
+
+};
+ARRAY_DEFINE_TYPE(setting_parser_info, struct setting_parser_info);
+
+/* name=NULL-terminated list of parsers. These follow the static settings.
+ After this list follows the actual settings. */
+struct dynamic_settings_parser {
+ const char *name;
+ const struct setting_parser_info *info;
+ size_t struct_offset;
+};
+ARRAY_DEFINE_TYPE(dynamic_settings_parser, struct dynamic_settings_parser);
+
+enum settings_parser_flags {
+ SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS = 0x01,
+ SETTINGS_PARSER_FLAG_TRACK_CHANGES = 0x02
+};
+
+struct setting_parser_context;
+
+struct setting_parser_context *
+settings_parser_init(pool_t set_pool, const struct setting_parser_info *root,
+ enum settings_parser_flags flags);
+struct setting_parser_context *
+settings_parser_init_list(pool_t set_pool,
+ const struct setting_parser_info *const *roots,
+ unsigned int count, enum settings_parser_flags flags);
+void settings_parser_deinit(struct setting_parser_context **ctx);
+
+/* Return pointer to root setting structure. */
+void *settings_parser_get(struct setting_parser_context *ctx);
+/* If there are multiple roots, return a NULL-terminated list to all of
+ their settings. */
+void **settings_parser_get_list(const struct setting_parser_context *ctx);
+/* Like settings_parser_get(), but return change struct. */
+void *settings_parser_get_changes(struct setting_parser_context *ctx);
+/* Returns the setting parser's roots (same as given to init()). */
+const struct setting_parser_info *const *
+settings_parser_get_roots(const struct setting_parser_context *ctx);
+
+/* Return the last error. */
+const char *settings_parser_get_error(struct setting_parser_context *ctx);
+/* Return the parser info used for the previously parsed line. */
+const struct setting_parser_info *
+settings_parse_get_prev_info(struct setting_parser_context *ctx);
+
+/* Returns TRUE if the given key is a valid setting. */
+bool settings_parse_is_valid_key(struct setting_parser_context *ctx,
+ const char *key);
+/* If key is an alias, return the primary key name. If key exists, return key
+ itself. If key doesn't exist, return NULL. */
+const char *settings_parse_unalias(struct setting_parser_context *ctx,
+ const char *key);
+/* Returns pointer to value for a key, or NULL if not found. */
+const void *
+settings_parse_get_value(struct setting_parser_context *ctx,
+ const char *key, enum setting_type *type_r);
+/* Returns TRUE if setting has been changed by this parser. */
+bool settings_parse_is_changed(struct setting_parser_context *ctx,
+ const char *key);
+/* Parse a single line. Returns 1 if OK, 0 if key is unknown, -1 if error. */
+int settings_parse_line(struct setting_parser_context *ctx, const char *line);
+/* Parse key/value pair. Returns 1 if OK, 0 if key is unknown, -1 if error. */
+int settings_parse_keyvalue(struct setting_parser_context *ctx,
+ const char *key, const char *value);
+/* Parse data already read in input stream. */
+int settings_parse_stream(struct setting_parser_context *ctx,
+ struct istream *input);
+/* Read data from input stream and parser it. returns -1 = error,
+ 0 = done, 1 = not finished yet (stream is non-blocking) */
+int settings_parse_stream_read(struct setting_parser_context *ctx,
+ struct istream *input);
+/* Open file and parse it. */
+int settings_parse_file(struct setting_parser_context *ctx,
+ const char *path, size_t max_line_length);
+int settings_parse_environ(struct setting_parser_context *ctx);
+/* Execute the given binary and wait for it to return the configuration. */
+int settings_parse_exec(struct setting_parser_context *ctx,
+ const char *bin_path, const char *config_path,
+ const char *service);
+/* Call all check_func()s to see if currently parsed settings are valid. */
+bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool,
+ const char **error_r);
+bool settings_check(const struct setting_parser_info *info, pool_t pool,
+ void *set, const char **error_r);
+
+/* While parsing values, specifies if STR_VARS strings are already expanded. */
+void settings_parse_set_expanded(struct setting_parser_context *ctx,
+ bool is_expanded);
+/* Mark all the parsed settings with given keys as being already expanded. */
+void settings_parse_set_key_expanded(struct setting_parser_context *ctx,
+ pool_t pool, const char *key);
+void settings_parse_set_keys_expanded(struct setting_parser_context *ctx,
+ pool_t pool, const char *const *keys);
+/* Update variable string pointers to skip over the '1' or '0'.
+ This is mainly useful when you want to run settings_parser_check() without
+ actually knowing what the variables are. */
+void settings_parse_var_skip(struct setting_parser_context *ctx);
+/* Expand all unexpanded variables using the given table. Update the string
+ pointers so that they can be used without skipping over the '1'.
+ Returns the same as var_expand(). */
+int settings_var_expand(const struct setting_parser_info *info,
+ void *set, pool_t pool,
+ const struct var_expand_table *table,
+ const char **error_r);
+int settings_var_expand_with_funcs(const struct setting_parser_info *info,
+ void *set, pool_t pool,
+ const struct var_expand_table *table,
+ const struct var_expand_func_table *func_table,
+ void *func_context, const char **error_r);
+/* Go through all the settings and return the first one that has an unexpanded
+ setting containing the given %key. */
+bool settings_vars_have_key(const struct setting_parser_info *info, void *set,
+ char var_key, const char *long_var_key,
+ const char **key_r, const char **value_r);
+/* Duplicate the entire settings structure. */
+void *settings_dup(const struct setting_parser_info *info,
+ const void *set, pool_t pool);
+/* Same as settings_dup(), but assume that the old pointers can still be safely
+ used. This saves memory since strings don't have to be duplicated. */
+void *settings_dup_with_pointers(const struct setting_parser_info *info,
+ const void *set, pool_t pool);
+/* Duplicate the entire setting parser. */
+struct setting_parser_context *
+settings_parser_dup(const struct setting_parser_context *old_ctx,
+ pool_t new_pool);
+
+/* parsers is a name=NULL -terminated list. The parsers are appended as
+ dynamic_settings_list structures to their parent. All must have the same
+ parent. The new structures are allocated from the given pool. */
+void settings_parser_info_update(pool_t pool,
+ struct setting_parser_info *parent,
+ const struct dynamic_settings_parser *parsers);
+void settings_parser_dyn_update(pool_t pool,
+ const struct setting_parser_info *const **roots,
+ const struct dynamic_settings_parser *dyn_parsers);
+
+/* Return pointer to beginning of settings for given name, or NULL if there is
+ no such registered name. */
+const void *settings_find_dynamic(const struct setting_parser_info *info,
+ const void *base_set, const char *name);
+
+/* Copy changed settings from src to dest. If conflict_key_r is not NULL and
+ both src and dest have changed the same setting, return -1 and set the
+ key name. If it's NULL, the old setting is kept.
+
+ KLUDGE: For SET_STRLIST types if both source and destination have identical
+ keys, the duplicates in the source side are ignored. This is required to
+ make the current config code work correctly. */
+int settings_parser_apply_changes(struct setting_parser_context *dest,
+ const struct setting_parser_context *src,
+ pool_t pool, const char **conflict_key_r);
+
+/* Return section name escaped */
+const char *settings_section_escape(const char *name);
+/* Parse time interval string, return as seconds. */
+int settings_get_time(const char *str, unsigned int *secs_r,
+ const char **error_r);
+/* Parse time interval string, return as milliseconds. */
+int settings_get_time_msecs(const char *str, unsigned int *msecs_r,
+ const char **error_r);
+/* Parse size string, return as bytes. */
+int settings_get_size(const char *str, uoff_t *bytes_r,
+ const char **error_r);
+
+#endif
diff --git a/src/lib-settings/settings.c b/src/lib-settings/settings.c
new file mode 100644
index 0000000..228fab7
--- /dev/null
+++ b/src/lib-settings/settings.c
@@ -0,0 +1,434 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "strescape.h"
+#include "settings.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#ifdef HAVE_GLOB_H
+# include <glob.h>
+#endif
+
+#ifndef GLOB_BRACE
+# define GLOB_BRACE 0
+#endif
+
+#define SECTION_ERRORMSG "%s (section changed in %s at line %d)"
+
+struct input_stack {
+ struct input_stack *prev;
+
+ struct istream *input;
+ const char *path;
+ unsigned int linenum;
+};
+
+settings_section_callback_t *null_settings_section_callback = NULL;
+
+static const char *get_bool(const char *value, bool *result)
+{
+ if (strcasecmp(value, "yes") == 0)
+ *result = TRUE;
+ else if (strcasecmp(value, "no") == 0)
+ *result = FALSE;
+ else
+ return t_strconcat("Invalid boolean: ", value, NULL);
+
+ return NULL;
+}
+
+static const char *get_uint(const char *value, unsigned int *result)
+{
+ int num;
+
+ if (sscanf(value, "%i", &num) != 1 || num < 0)
+ return t_strconcat("Invalid number: ", value, NULL);
+ *result = num;
+ return NULL;
+}
+
+#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')
+
+static const char *expand_environment_vars(const char *value)
+{
+ const char *pvalue = value, *p;
+
+ /* Fast path when there are no candidates */
+ if ((pvalue = strchr(pvalue, '$')) == NULL)
+ return value;
+
+ string_t *expanded_value = t_str_new(strlen(value));
+ str_append_data(expanded_value, value, pvalue - value);
+
+ while (pvalue != NULL && (p = strchr(pvalue, '$')) != NULL) {
+ const char *var_end;
+ str_append_data(expanded_value, pvalue, p - pvalue);
+ if ((p == value || IS_WHITE(p[-1])) &&
+ str_begins(p, "$ENV:")) {
+ const char *var_name, *envval;
+ var_end = strchr(p, ' ');
+ if (var_end == NULL)
+ var_name = p + 5;
+ else
+ var_name = t_strdup_until(p + 5, var_end);
+ if ((envval = getenv(var_name)) != NULL)
+ str_append(expanded_value, envval);
+ } else {
+ str_append_c(expanded_value, '$');
+ var_end = p + 1;
+ }
+ pvalue = var_end;
+ }
+
+ if (pvalue != NULL)
+ str_append(expanded_value, pvalue);
+
+ return str_c(expanded_value);
+}
+
+const char *
+parse_setting_from_defs(pool_t pool, const struct setting_def *defs, void *base,
+ const char *key, const char *value)
+{
+ const struct setting_def *def;
+
+ for (def = defs; def->name != NULL; def++) {
+ if (strcmp(def->name, key) == 0) {
+ void *ptr = STRUCT_MEMBER_P(base, def->offset);
+
+ switch (def->type) {
+ case SET_STR:
+ *((char **)ptr) = p_strdup(pool, value);
+ return NULL;
+ case SET_INT:
+ /* use %i so we can handle eg. 0600
+ as octal value with umasks */
+ return get_uint(value, (unsigned int *) ptr);
+ case SET_BOOL:
+ return get_bool(value, (bool *) ptr);
+ }
+ }
+ }
+
+ return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static const char *
+fix_relative_path(const char *path, struct input_stack *input)
+{
+ const char *p;
+
+ if (*path == '/')
+ return path;
+
+ p = strrchr(input->path, '/');
+ if (p == NULL)
+ return path;
+
+ return t_strconcat(t_strdup_until(input->path, p+1), path, NULL);
+}
+
+static int settings_add_include(const char *path, struct input_stack **inputp,
+ bool ignore_errors, const char **error_r)
+{
+ struct input_stack *tmp, *new_input;
+ int fd;
+
+ for (tmp = *inputp; tmp != NULL; tmp = tmp->prev) {
+ if (strcmp(tmp->path, path) == 0)
+ break;
+ }
+ if (tmp != NULL) {
+ *error_r = t_strdup_printf("Recursive include file: %s", path);
+ return -1;
+ }
+
+ if ((fd = open(path, O_RDONLY)) == -1) {
+ if (ignore_errors)
+ return 0;
+
+ *error_r = t_strdup_printf("Couldn't open include file %s: %m",
+ path);
+ return -1;
+ }
+
+ new_input = t_new(struct input_stack, 1);
+ new_input->prev = *inputp;
+ new_input->path = t_strdup(path);
+ new_input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ i_stream_set_return_partial_line(new_input->input, TRUE);
+ *inputp = new_input;
+ return 0;
+}
+
+static int
+settings_include(const char *pattern, struct input_stack **inputp,
+ bool ignore_errors, const char **error_r)
+{
+#ifdef HAVE_GLOB
+ glob_t globbers;
+ unsigned int i;
+
+ switch (glob(pattern, GLOB_BRACE, NULL, &globbers)) {
+ case 0:
+ break;
+ case GLOB_NOSPACE:
+ *error_r = "glob() failed: Not enough memory";
+ return -1;
+ case GLOB_ABORTED:
+ *error_r = "glob() failed: Read error";
+ return -1;
+ case GLOB_NOMATCH:
+ if (ignore_errors)
+ return 0;
+ *error_r = "No matches";
+ return -1;
+ default:
+ *error_r = "glob() failed: Unknown error";
+ return -1;
+ }
+
+ /* iterate through the different files matching the globbing */
+ for (i = 0; i < globbers.gl_pathc; i++) {
+ if (settings_add_include(globbers.gl_pathv[i], inputp,
+ ignore_errors, error_r) < 0)
+ return -1;
+ }
+ globfree(&globbers);
+ return 0;
+#else
+ return settings_add_include(pattern, inputp, ignore_errors, error_r);
+#endif
+}
+
+bool settings_read_i(const char *path, const char *section,
+ settings_callback_t *callback,
+ settings_section_callback_t *sect_callback, void *context,
+ const char **error_r)
+{
+ /* pretty horrible code, but v2.0 will have this rewritten anyway.. */
+ struct input_stack root, *input;
+ const char *errormsg, *next_section, *name, *last_section_path = NULL;
+ char *line, *key, *p, quote;
+ string_t *full_line;
+ size_t len;
+ int fd, last_section_line = 0, skip, sections, root_section;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ *error_r = t_strdup_printf(
+ "Can't open configuration file %s: %m", path);
+ return FALSE;
+ }
+
+ if (section == NULL) {
+ skip = 0;
+ next_section = NULL;
+ } else {
+ skip = 1;
+ next_section = t_strcut(section, '/');
+ }
+
+ i_zero(&root);
+ root.path = path;
+ input = &root;
+
+ full_line = t_str_new(512);
+ sections = 0; root_section = 0; errormsg = NULL;
+ input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ i_stream_set_return_partial_line(input->input, TRUE);
+prevfile:
+ while ((line = i_stream_read_next_line(input->input)) != NULL) {
+ input->linenum++;
+
+ /* @UNSAFE: line is modified */
+
+ /* skip whitespace */
+ while (IS_WHITE(*line))
+ line++;
+
+ /* ignore comments or empty lines */
+ if (*line == '#' || *line == '\0')
+ continue;
+
+ /* strip away comments. pretty kludgy way really.. */
+ for (p = line; *p != '\0'; p++) {
+ if (*p == '\'' || *p == '"') {
+ quote = *p;
+ for (p++; *p != quote && *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0')
+ p++;
+ }
+ if (*p == '\0')
+ break;
+ } else if (*p == '#') {
+ if (!IS_WHITE(p[-1])) {
+ i_warning("Configuration file %s line %u: "
+ "Ambiguous '#' character in line, treating it as comment. "
+ "Add a space before it to remove this warning.",
+ input->path, input->linenum);
+ }
+ *p = '\0';
+ break;
+ }
+ }
+
+ /* remove whitespace from end of line */
+ len = strlen(line);
+ while (IS_WHITE(line[len-1]))
+ len--;
+ line[len] = '\0';
+
+ if (len > 0 && line[len-1] == '\\') {
+ /* continues in next line */
+ len--;
+ while (IS_WHITE(line[len-1]))
+ len--;
+ str_append_data(full_line, line, len);
+ str_append_c(full_line, ' ');
+ continue;
+ }
+ if (str_len(full_line) > 0) {
+ str_append(full_line, line);
+ line = str_c_modifiable(full_line);
+ }
+
+ bool quoted = FALSE;
+ /* a) key = value
+ b) section_type [section_name] {
+ c) } */
+ key = line;
+ while (!IS_WHITE(*line) && *line != '\0' && *line != '=')
+ line++;
+ if (IS_WHITE(*line)) {
+ *line++ = '\0';
+ while (IS_WHITE(*line)) line++;
+ }
+
+ if (strcmp(key, "!include_try") == 0 ||
+ strcmp(key, "!include") == 0) {
+ if (settings_include(fix_relative_path(line, input),
+ &input,
+ strcmp(key, "!include_try") == 0,
+ &errormsg) == 0)
+ goto prevfile;
+ } else if (*line == '=') {
+ /* a) */
+ *line++ = '\0';
+ while (IS_WHITE(*line)) line++;
+
+ len = strlen(line);
+ if (len > 0 &&
+ ((*line == '"' && line[len-1] == '"') ||
+ (*line == '\'' && line[len-1] == '\''))) {
+ line[len-1] = '\0';
+ line = str_unescape(line+1);
+ quoted = TRUE;
+ }
+
+ /* @UNSAFE: Cast to modifiable datastack value,
+ but it will not be actually modified after this. */
+ if (!quoted)
+ line = (char *)expand_environment_vars(line);
+
+ errormsg = skip > 0 ? NULL :
+ callback(key, line, context);
+ } else if (strcmp(key, "}") != 0 || *line != '\0') {
+ /* b) + errors */
+ line[-1] = '\0';
+
+ if (*line == '{')
+ name = "";
+ else {
+ name = line;
+ while (!IS_WHITE(*line) && *line != '\0')
+ line++;
+
+ if (*line != '\0') {
+ *line++ = '\0';
+ while (IS_WHITE(*line))
+ line++;
+ }
+ }
+
+ if (*line != '{')
+ errormsg = "Expecting '='";
+ else {
+ sections++;
+ if (next_section != NULL &&
+ strcmp(next_section, name) == 0) {
+ section += strlen(next_section);
+ if (*section == '\0') {
+ skip = 0;
+ next_section = NULL;
+ root_section = sections;
+ } else {
+ i_assert(*section == '/');
+ section++;
+ next_section =
+ t_strcut(section, '/');
+ }
+ }
+
+ if (skip > 0)
+ skip++;
+ else {
+ skip = sect_callback == NULL ? 1 :
+ !sect_callback(key, name,
+ context,
+ &errormsg);
+ if (errormsg != NULL &&
+ last_section_line != 0) {
+ errormsg = t_strdup_printf(
+ SECTION_ERRORMSG,
+ errormsg,
+ last_section_path,
+ last_section_line);
+ }
+ }
+ last_section_path = input->path;
+ last_section_line = input->linenum;
+ }
+ } else {
+ /* c) */
+ if (sections == 0)
+ errormsg = "Unexpected '}'";
+ else {
+ if (skip > 0)
+ skip--;
+ else {
+ i_assert(sect_callback != NULL);
+ sect_callback(NULL, NULL, context,
+ &errormsg);
+ if (root_section == sections &&
+ errormsg == NULL) {
+ /* we found the section,
+ now quit */
+ break;
+ }
+ }
+ last_section_path = input->path;
+ last_section_line = input->linenum;
+ sections--;
+ }
+ }
+
+ if (errormsg != NULL) {
+ *error_r = t_strdup_printf(
+ "Error in configuration file %s line %d: %s",
+ input->path, input->linenum, errormsg);
+ break;
+ }
+ str_truncate(full_line, 0);
+ }
+
+ i_stream_destroy(&input->input);
+ input = input->prev;
+ if (line == NULL && input != NULL)
+ goto prevfile;
+
+ return errormsg == NULL;
+}
diff --git a/src/lib-settings/settings.h b/src/lib-settings/settings.h
new file mode 100644
index 0000000..888dfb0
--- /dev/null
+++ b/src/lib-settings/settings.h
@@ -0,0 +1,73 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+/*
+ * Note:
+ *
+ * The definitions in this file are used for parsing of external config
+ * files and *not* for parsing of dovecot.conf. Unfortunately, the types
+ * here (e.g., enum settings_type) collide with those in settings-parser.h.
+ *
+ * We should remove the need for this file in v3.0.
+ */
+
+enum setting_type {
+ SET_STR,
+ SET_INT,
+ SET_BOOL
+};
+
+struct setting_def {
+ enum setting_type type;
+ const char *name;
+ size_t offset;
+};
+
+#define DEF_STRUCT_STR(name, struct_name) \
+ { SET_STR + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
+ ((struct struct_name *)0)->name, const char *), \
+ #name, offsetof(struct struct_name, name) }
+#define DEF_STRUCT_INT(name, struct_name) \
+ { SET_INT + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
+ ((struct struct_name *)0)->name, unsigned int), \
+ #name, offsetof(struct struct_name, name) }
+#define DEF_STRUCT_BOOL(name, struct_name) \
+ { SET_BOOL + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
+ ((struct struct_name *)0)->name, bool), \
+ #name, offsetof(struct struct_name, name) }
+
+/* Return error message. When closing section, key = NULL, value = NULL. */
+typedef const char *settings_callback_t(const char *key, const char *value,
+ void *context);
+
+/* Return TRUE if we want to go inside the section */
+typedef bool settings_section_callback_t(const char *type, const char *name,
+ void *context, const char **errormsg);
+
+extern settings_section_callback_t *null_settings_section_callback;
+
+const char *
+parse_setting_from_defs(pool_t pool, const struct setting_def *defs, void *base,
+ const char *key, const char *value);
+
+bool settings_read_i(const char *path, const char *section,
+ settings_callback_t *callback,
+ settings_section_callback_t *sect_callback, void *context,
+ const char **error_r)
+ ATTR_NULL(2, 4, 5);
+#define settings_read(path, section, callback, sect_callback, context, error_r) \
+ settings_read_i(path - \
+ CALLBACK_TYPECHECK(callback, const char *(*)( \
+ const char *, const char *, typeof(context))) - \
+ CALLBACK_TYPECHECK(sect_callback, bool (*)( \
+ const char *, const char *, typeof(context), \
+ const char **)), \
+ section, (settings_callback_t *)callback, \
+ (settings_section_callback_t *)sect_callback, context, error_r)
+#define settings_read_nosection(path, callback, context, error_r) \
+ settings_read_i(path - \
+ CALLBACK_TYPECHECK(callback, const char *(*)( \
+ const char *, const char *, typeof(context))), \
+ NULL, (settings_callback_t *)callback, NULL, context, error_r)
+
+#endif
diff --git a/src/lib-settings/test-settings-parser.c b/src/lib-settings/test-settings-parser.c
new file mode 100644
index 0000000..83aefba
--- /dev/null
+++ b/src/lib-settings/test-settings-parser.c
@@ -0,0 +1,340 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "net.h"
+#include "var-expand.h"
+#include "settings-parser.h"
+#include "istream.h"
+#include "test-common.h"
+
+static const char *test_settings_blobs[] =
+{
+/* Blob 0 */
+ "bool_true=yes\n"
+ "bool_false=no\n"
+ "uint=15\n"
+ "uint_oct=0700\n"
+ "secs=5s\n"
+ "msecs=5ms\n"
+ "size=1k\n"
+ "port=2205\n"
+ "str=test string\n"
+ "expand_str=test %{string}\n"
+ "strlist=\n"
+ "strlist/x=a\n"
+ "strlist/y=b\n"
+ "strlist/z=c\n"
+ "\n",
+};
+
+
+static void test_settings_get_time(void)
+{
+ static const struct {
+ const char *input;
+ unsigned int output;
+ } tests[] = {
+ { "0", 0 },
+
+ { "59s", 59 },
+ { "59 s", 59 },
+ { "59se", 59 },
+ { "59sec", 59 },
+ { "59secs", 59 },
+ { "59seco", 59 },
+ { "59secon", 59 },
+ { "59second", 59 },
+ { "59seconds", 59 },
+ { "123456 seconds", 123456 },
+
+ { "123m", 123*60 },
+ { "123 m", 123*60 },
+ { "123 mi", 123*60 },
+ { "123 min", 123*60 },
+ { "123 mins", 123*60 },
+ { "123 minu", 123*60 },
+ { "123 minut", 123*60 },
+ { "123 minute", 123*60 },
+ { "123 minutes", 123*60 },
+
+ { "123h", 123*60*60 },
+ { "123 h", 123*60*60 },
+ { "123 ho", 123*60*60 },
+ { "123 hou", 123*60*60 },
+ { "123 hour", 123*60*60 },
+ { "123 hours", 123*60*60 },
+
+ { "12d", 12*60*60*24 },
+ { "12 d", 12*60*60*24 },
+ { "12 da", 12*60*60*24 },
+ { "12 day", 12*60*60*24 },
+ { "12 days", 12*60*60*24 },
+
+ { "3w", 3*60*60*24*7 },
+ { "3 w", 3*60*60*24*7 },
+ { "3 we", 3*60*60*24*7 },
+ { "3 wee", 3*60*60*24*7 },
+ { "3 week", 3*60*60*24*7 },
+ { "3 weeks", 3*60*60*24*7 },
+
+ { "1000ms", 1 },
+ { "50000ms", 50 },
+ };
+ struct {
+ const char *input;
+ unsigned int output;
+ } msecs_tests[] = {
+ { "0ms", 0 },
+ { "1ms", 1 },
+ { "123456ms", 123456 },
+ { "123456 ms", 123456 },
+ { "123456mse", 123456 },
+ { "123456msec", 123456 },
+ { "123456msecs", 123456 },
+ { "123456mseco", 123456 },
+ { "123456msecon", 123456 },
+ { "123456msecond", 123456 },
+ { "123456mseconds", 123456 },
+ { "123456mil", 123456 },
+ { "123456mill", 123456 },
+ { "123456milli", 123456 },
+ { "123456millis", 123456 },
+ { "123456millisec", 123456 },
+ { "123456millisecs", 123456 },
+ { "123456milliseco", 123456 },
+ { "123456millisecon", 123456 },
+ { "123456millisecond", 123456 },
+ { "123456milliseconds", 123456 },
+ { "4294967295 ms", 4294967295 },
+ };
+ const char *secs_errors[] = {
+ "-1",
+ "1",
+ /* wrong spellings: */
+ "1ss",
+ "1secss",
+ "1secondss",
+ "1ma",
+ "1minsa",
+ "1hu",
+ "1hoursa",
+ "1dd",
+ "1days?",
+ "1wa",
+ "1weeksb",
+
+ /* milliseconds: */
+ "1ms",
+ "999ms",
+ "1001ms",
+ /* overflows: */
+ "7102 w",
+ "4294967296 s",
+ };
+ const char *msecs_errors[] = {
+ "-1",
+ "1",
+ /* wrong spellings: */
+ "1mis",
+ "1mss",
+ /* overflows: */
+ "8 w",
+ "4294967296 ms",
+ };
+ unsigned int i, secs, msecs;
+ const char *error;
+
+ test_begin("settings_get_time()");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ test_assert_idx(settings_get_time(tests[i].input, &secs, &error) == 0, i);
+ test_assert_idx(secs == tests[i].output, i);
+
+ test_assert_idx(settings_get_time_msecs(tests[i].input, &msecs, &error) == 0, i);
+ test_assert_idx(msecs == tests[i].output*1000, i);
+ }
+ for (i = 0; i < N_ELEMENTS(msecs_tests); i++) {
+ test_assert_idx(settings_get_time_msecs(msecs_tests[i].input, &msecs, &error) == 0, i);
+ test_assert_idx(msecs == msecs_tests[i].output, i);
+ }
+ for (i = 0; i < N_ELEMENTS(secs_errors); i++)
+ test_assert_idx(settings_get_time(secs_errors[i], &secs, &error) < 0, i);
+ for (i = 0; i < N_ELEMENTS(msecs_errors); i++)
+ test_assert_idx(settings_get_time_msecs(msecs_errors[i], &msecs, &error) < 0, i);
+ test_end();
+}
+
+static void test_settings_get_size(void)
+{
+ test_begin("settings_get_size()");
+
+ static const struct {
+ const char *input;
+ uoff_t output;
+ } tests[] = {
+ { "0", 0 },
+ { "0000", 0 },
+ { "1b", 1 },
+ { "1B", 1 },
+ { "1 b", 1 },
+ { "1k", 1024 },
+ { "1K", 1024 },
+ { "1 k", 1024 },
+ { "1m", 1024*1024 },
+ { "1M", 1024*1024 },
+ { "1 m", 1024*1024 },
+ { "1g", 1024*1024*1024ULL },
+ { "1G", 1024*1024*1024ULL },
+ { "1 g", 1024*1024*1024ULL },
+ { "1t", 1024*1024*1024*1024ULL },
+ { "1T", 1024*1024*1024*1024ULL },
+ { "1 t", 1024*1024*1024*1024ULL },
+ };
+
+ const char *size_errors[] = {
+ "-1",
+ "one",
+ "",
+ "340282366920938463463374607431768211456",
+ "2^32",
+ "2**32",
+ "1e10",
+ "1 byte",
+ };
+
+ size_t i;
+ uoff_t size;
+ const char *error;
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ error = NULL;
+ test_assert_idx(settings_get_size(tests[i].input, &size, &error) == 0, i);
+ test_assert_idx(size == tests[i].output, i);
+ test_assert(error == NULL);
+ }
+ for (i = 0; i < N_ELEMENTS(size_errors); i++) {
+ error = NULL;
+ test_assert_idx(settings_get_size(size_errors[i], &size, &error) < 0, i);
+ test_assert(error != NULL);
+ };
+
+ test_end();
+}
+
+static void test_settings_parser_get(void)
+{
+ struct test_settings {
+ bool bool_true;
+ bool bool_false;
+ unsigned int uint;
+ unsigned int uint_oct;
+ unsigned int secs;
+ unsigned int msecs;
+ uoff_t size;
+ in_port_t port;
+ const char *str;
+ const char *expand_str;
+ ARRAY_TYPE(const_string) strlist;
+ } test_defaults = {
+ FALSE, /* for negation test */
+ TRUE,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ "",
+ ARRAY_INIT,
+ };
+ const struct setting_define defs[] = {
+ SETTING_DEFINE_STRUCT_BOOL("bool_true", bool_true, struct test_settings),
+ SETTING_DEFINE_STRUCT_BOOL("bool_false", bool_false, struct test_settings),
+ SETTING_DEFINE_STRUCT_UINT("uint", uint, struct test_settings),
+ { .type = SET_UINT_OCT, .key = "uint_oct",
+ offsetof(struct test_settings, uint_oct), NULL },
+ SETTING_DEFINE_STRUCT_TIME("secs", secs, struct test_settings),
+ SETTING_DEFINE_STRUCT_TIME_MSECS("msecs", msecs, struct test_settings),
+ SETTING_DEFINE_STRUCT_SIZE("size", size, struct test_settings),
+ SETTING_DEFINE_STRUCT_IN_PORT("port", port, struct test_settings),
+ SETTING_DEFINE_STRUCT_STR("str", str, struct test_settings),
+ { .type = SET_STR_VARS, .key = "expand_str",
+ offsetof(struct test_settings, expand_str), NULL },
+ { .type = SET_STRLIST, .key = "strlist",
+ offsetof(struct test_settings, strlist), NULL },
+ SETTING_DEFINE_LIST_END
+ };
+ const struct setting_parser_info root = {
+ .module_name = "test",
+ .defines = defs,
+ .defaults = &test_defaults,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct test_settings),
+
+ .parent_offset = SIZE_MAX,
+ };
+
+ test_begin("settings_parser_get");
+
+ pool_t pool = pool_alloconly_create("settings parser", 1024);
+ struct setting_parser_context *ctx =
+ settings_parser_init(pool, &root, 0);
+ struct istream *is = test_istream_create(test_settings_blobs[0]);
+ const char *error = NULL;
+ int ret;
+ while((ret = settings_parse_stream_read(ctx, is)) > 0);
+ test_assert(ret == 0);
+ if (ret < 0)
+ i_error("settings_parse_stream failed: %s",
+ settings_parser_get_error(ctx));
+ i_stream_unref(&is);
+ test_assert(settings_parser_check(ctx, pool, NULL));
+
+ /* check what we got */
+ struct test_settings *settings = settings_parser_get(ctx);
+ test_assert(settings != NULL);
+
+ test_assert(settings->bool_true == TRUE);
+ test_assert(settings->bool_false == FALSE);
+ test_assert(settings->uint == 15);
+ test_assert(settings->uint_oct == 0700);
+ test_assert(settings->secs == 5);
+ test_assert(settings->msecs == 5);
+ test_assert(settings->size == 1024);
+ test_assert(settings->port == 2205);
+ test_assert_strcmp(settings->str, "test string");
+ test_assert_strcmp(settings->expand_str, "0test %{string}");
+
+ test_assert(array_count(&settings->strlist) == 6);
+ test_assert_strcmp(t_array_const_string_join(&settings->strlist, ";"),
+ "x;a;y;b;z;c");
+
+ const struct var_expand_table table[] = {
+ {'\0', "value", "string"},
+ {'\0', NULL, NULL}
+ };
+
+ /* expand settings */
+ test_assert(settings_var_expand(&root, settings, pool, table, &error) == 1 &&
+ error == NULL);
+
+ /* check that the setting got expanded */
+ test_assert_strcmp(settings->expand_str, "test value");
+
+ settings_parser_deinit(&ctx);
+ pool_unref(&pool);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_settings_get_time,
+ test_settings_get_size,
+ test_settings_parser_get,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-settings/test-settings.c b/src/lib-settings/test-settings.c
new file mode 100644
index 0000000..4ad2a6b
--- /dev/null
+++ b/src/lib-settings/test-settings.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "net.h"
+#include "settings.h"
+#include "istream.h"
+#include "ostream.h"
+#include "test-common.h"
+
+#define TEST_SETTING_FILE ".test_settings.conf"
+
+static const char *config_contents =
+"# this is a comment\n"
+"str = value\n"
+"str2 = some other value # and this should be ignored\n"
+"str3 = $ENV:test\n"
+"str4 = $ENV:test %{second}\n"
+"str5 = Hello $ENV:test\n"
+"str6 = foo$ENV:test bar\n"
+"str7 = \"this is $ENV:test string literal\"\n"
+"str8 = \\$ENV:test escaped\n"
+"str9 = $ENV:FOO$ENV:FOO bar\n"
+"str10 = \\$escape \\escape \\\"escape\\\"\n"
+"str11 = 'this is $ENV:test string literal'\n"
+"str12 = $ENV:test $ENV:test\n"
+"b_true = yes\n"
+"b_false = no\n"
+"number = 1234\n";
+
+struct test_settings {
+ const char *str;
+ const char *str2;
+ const char *str3;
+ const char *str4;
+ const char *str5;
+ const char *str6;
+ const char *str7;
+ const char *str8;
+ const char *str9;
+ const char *str10;
+ const char *str11;
+ const char *str12;
+
+ bool b_true;
+ bool b_false;
+ unsigned int number;
+};
+
+#undef DEF_STR
+#undef DEF_BOOL
+#undef DEF_INT
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, test_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, test_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, test_settings)
+
+static struct setting_def setting_defs[] = {
+ DEF_STR(str),
+ DEF_STR(str2),
+ DEF_STR(str3),
+ DEF_STR(str4),
+ DEF_STR(str5),
+ DEF_STR(str6),
+ DEF_STR(str7),
+ DEF_STR(str8),
+ DEF_STR(str9),
+ DEF_STR(str10),
+ DEF_STR(str11),
+ DEF_STR(str12),
+ DEF_BOOL(b_true),
+ DEF_BOOL(b_false),
+ DEF_INT(number),
+ { 0, NULL, 0 }
+};
+
+static struct test_settings default_settings = {
+ .str = "",
+ .str2 = "",
+ .str3 = "",
+ .str4 = "",
+ .str5 = "",
+ .str6 = "",
+ .str7 = "",
+ .str8 = "",
+ .str9 = "",
+ .str10 = "",
+ .str11 = "",
+ .str12 = "",
+
+ .b_true = FALSE,
+ .b_false = TRUE,
+ .number = 0,
+};
+
+struct test_settings_context {
+ pool_t pool;
+ struct test_settings set;
+};
+
+static const char *parse_setting(const char *key, const char *value,
+ struct test_settings_context *ctx)
+{
+ return parse_setting_from_defs(ctx->pool, setting_defs,
+ &ctx->set, key, value);
+}
+
+static void test_settings_read_nosection(void)
+{
+ test_begin("settings_read_nosection");
+
+ const char *error = NULL;
+ /* write a simple config file */
+ struct ostream *os = o_stream_create_file(TEST_SETTING_FILE, 0, 0600, 0);
+ o_stream_nsend_str(os, config_contents);
+ test_assert(o_stream_finish(os) == 1);
+ o_stream_unref(&os);
+
+ putenv("test=first");
+ putenv("FOO$ENV:FOO=works");
+ /* try parse it */
+ pool_t pool = pool_alloconly_create("test settings", 1024);
+ struct test_settings_context *ctx =
+ p_new(pool, struct test_settings_context, 1);
+ ctx->pool = pool;
+ ctx->set = default_settings;
+
+ test_assert(settings_read_nosection(TEST_SETTING_FILE, parse_setting,
+ ctx, &error));
+ test_assert(error == NULL);
+ if (error != NULL)
+ i_error("%s", error);
+
+ /* see what we got */
+ test_assert_strcmp(ctx->set.str, "value");
+ test_assert_strcmp(ctx->set.str2, "some other value");
+ test_assert_strcmp(ctx->set.str3, "first");
+ test_assert_strcmp(ctx->set.str4, "first %{second}");
+ test_assert_strcmp(ctx->set.str5, "Hello first");
+ test_assert_strcmp(ctx->set.str6, "foo$ENV:test bar");
+ test_assert_strcmp(ctx->set.str7, "this is $ENV:test string literal");
+ test_assert_strcmp(ctx->set.str8, "\\$ENV:test escaped");
+ test_assert_strcmp(ctx->set.str9, "works bar");
+ test_assert_strcmp(ctx->set.str10, "\\$escape \\escape \\\"escape\\\"");
+ test_assert_strcmp(ctx->set.str11, "this is $ENV:test string literal");
+ test_assert_strcmp(ctx->set.str12, "first first");
+
+ test_assert(ctx->set.b_true == TRUE);
+ test_assert(ctx->set.b_false == FALSE);
+ test_assert(ctx->set.number == 1234);
+
+ pool_unref(&pool);
+
+ i_unlink_if_exists(TEST_SETTING_FILE);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_settings_read_nosection,
+ NULL
+ };
+ return test_run(test_functions);
+}