diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-settings/Makefile.am | 40 | ||||
-rw-r--r-- | src/lib-settings/Makefile.in | 870 | ||||
-rw-r--r-- | src/lib-settings/settings-parser.c | 2226 | ||||
-rw-r--r-- | src/lib-settings/settings-parser.h | 281 | ||||
-rw-r--r-- | src/lib-settings/settings.c | 434 | ||||
-rw-r--r-- | src/lib-settings/settings.h | 73 | ||||
-rw-r--r-- | src/lib-settings/test-settings-parser.c | 340 | ||||
-rw-r--r-- | src/lib-settings/test-settings.c | 165 |
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..212f721 --- /dev/null +++ b/src/lib-settings/Makefile.in @@ -0,0 +1,870 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 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); +} |