diff options
Diffstat (limited to 'src/lib-lua')
-rw-r--r-- | src/lib-lua/Makefile.am | 68 | ||||
-rw-r--r-- | src/lib-lua/Makefile.in | 971 | ||||
-rw-r--r-- | src/lib-lua/dlua-compat.c | 157 | ||||
-rw-r--r-- | src/lib-lua/dlua-compat.h | 69 | ||||
-rw-r--r-- | src/lib-lua/dlua-dovecot-http.c | 519 | ||||
-rw-r--r-- | src/lib-lua/dlua-dovecot.c | 681 | ||||
-rw-r--r-- | src/lib-lua/dlua-error.c | 13 | ||||
-rw-r--r-- | src/lib-lua/dlua-pushstring.c | 26 | ||||
-rw-r--r-- | src/lib-lua/dlua-resume.c | 208 | ||||
-rw-r--r-- | src/lib-lua/dlua-script-private.h | 264 | ||||
-rw-r--r-- | src/lib-lua/dlua-script.c | 453 | ||||
-rw-r--r-- | src/lib-lua/dlua-script.h | 28 | ||||
-rw-r--r-- | src/lib-lua/dlua-table.c | 301 | ||||
-rw-r--r-- | src/lib-lua/dlua-thread.c | 276 | ||||
-rw-r--r-- | src/lib-lua/dlua-wrapper.h | 186 | ||||
-rw-r--r-- | src/lib-lua/test-dict-lua.c | 99 | ||||
-rw-r--r-- | src/lib-lua/test-lua.c | 473 |
17 files changed, 4792 insertions, 0 deletions
diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am new file mode 100644 index 0000000..3416489 --- /dev/null +++ b/src/lib-lua/Makefile.am @@ -0,0 +1,68 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-master \ + $(LUA_CFLAGS) + +pkglib_LTLIBRARIES = libdovecot-lua.la +libdovecot_lua_la_SOURCES = \ + dlua-script.c \ + dlua-pushstring.c \ + dlua-error.c \ + dlua-dovecot.c \ + dlua-dovecot-http.c \ + dlua-compat.c \ + dlua-resume.c \ + dlua-table.c \ + dlua-thread.c + +test_programs = test-lua + +LIBDICT_LUA= +if DLUA_WITH_YIELDS +LIBDICT_LUA += ../lib-dict/libdict_lua.la +test_programs += test-dict-lua +endif + +# Note: the only things this lib should depend on are libdovecot and lua. +libdovecot_lua_la_DEPENDENCIES = \ + ../lib-dovecot/libdovecot.la \ + $(LIBDICT_LUA) +libdovecot_lua_la_LIBADD = \ + ../lib-dovecot/libdovecot.la \ + $(LIBDICT_LUA) \ + $(LUA_LIBS) +libdovecot_lua_la_LDFLAGS = -export-dynamic + +headers = \ + dlua-compat.h \ + dlua-script.h \ + dlua-script-private.h \ + dlua-wrapper.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +noinst_PROGRAMS = $(test_programs) + +test_libs =\ + libdovecot-lua.la \ + ../lib-dovecot/libdovecot.la + +test_lua_SOURCES = test-lua.c +test_lua_CFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +test_lua_LDFLAGS = $(BINARY_LDFLAGS) +test_lua_LDADD = $(test_libs) $(LUA_LIBS) +test_lua_DEPENDENCIES = $(test_libs) + +test_dict_lua_SOURCES = test-dict-lua.c +test_dict_lua_LDADD = $(test_libs) $(LUA_LIBS) +test_dict_lua_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-lua/Makefile.in b/src/lib-lua/Makefile.in new file mode 100644 index 0000000..687319a --- /dev/null +++ b/src/lib-lua/Makefile.in @@ -0,0 +1,971 @@ +# 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@ +@DLUA_WITH_YIELDS_TRUE@am__append_1 = ../lib-dict/libdict_lua.la +@DLUA_WITH_YIELDS_TRUE@am__append_2 = test-dict-lua +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib-lua +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 = +@DLUA_WITH_YIELDS_TRUE@am__EXEEXT_1 = test-dict-lua$(EXEEXT) +am__EXEEXT_2 = test-lua$(EXEEXT) $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +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)$(pkglibdir)" \ + "$(DESTDIR)$(pkginc_libdir)" +LTLIBRARIES = $(pkglib_LTLIBRARIES) +am__DEPENDENCIES_1 = +am_libdovecot_lua_la_OBJECTS = dlua-script.lo dlua-pushstring.lo \ + dlua-error.lo dlua-dovecot.lo dlua-dovecot-http.lo \ + dlua-compat.lo dlua-resume.lo dlua-table.lo dlua-thread.lo +libdovecot_lua_la_OBJECTS = $(am_libdovecot_lua_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 = +libdovecot_lua_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libdovecot_lua_la_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_test_dict_lua_OBJECTS = test-dict-lua.$(OBJEXT) +test_dict_lua_OBJECTS = $(am_test_dict_lua_OBJECTS) +am_test_lua_OBJECTS = test_lua-test-lua.$(OBJEXT) +test_lua_OBJECTS = $(am_test_lua_OBJECTS) +test_lua_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_lua_CFLAGS) \ + $(CFLAGS) $(test_lua_LDFLAGS) $(LDFLAGS) -o $@ +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)/dlua-compat.Plo \ + ./$(DEPDIR)/dlua-dovecot-http.Plo ./$(DEPDIR)/dlua-dovecot.Plo \ + ./$(DEPDIR)/dlua-error.Plo ./$(DEPDIR)/dlua-pushstring.Plo \ + ./$(DEPDIR)/dlua-resume.Plo ./$(DEPDIR)/dlua-script.Plo \ + ./$(DEPDIR)/dlua-table.Plo ./$(DEPDIR)/dlua-thread.Plo \ + ./$(DEPDIR)/test-dict-lua.Po ./$(DEPDIR)/test_lua-test-lua.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 = $(libdovecot_lua_la_SOURCES) $(test_dict_lua_SOURCES) \ + $(test_lua_SOURCES) +DIST_SOURCES = $(libdovecot_lua_la_SOURCES) $(test_dict_lua_SOURCES) \ + $(test_lua_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +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@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-master \ + $(LUA_CFLAGS) + +pkglib_LTLIBRARIES = libdovecot-lua.la +libdovecot_lua_la_SOURCES = \ + dlua-script.c \ + dlua-pushstring.c \ + dlua-error.c \ + dlua-dovecot.c \ + dlua-dovecot-http.c \ + dlua-compat.c \ + dlua-resume.c \ + dlua-table.c \ + dlua-thread.c + +test_programs = test-lua $(am__append_2) +LIBDICT_LUA = $(am__append_1) + +# Note: the only things this lib should depend on are libdovecot and lua. +libdovecot_lua_la_DEPENDENCIES = \ + ../lib-dovecot/libdovecot.la \ + $(LIBDICT_LUA) + +libdovecot_lua_la_LIBADD = \ + ../lib-dovecot/libdovecot.la \ + $(LIBDICT_LUA) \ + $(LUA_LIBS) + +libdovecot_lua_la_LDFLAGS = -export-dynamic +headers = \ + dlua-compat.h \ + dlua-script.h \ + dlua-script-private.h \ + dlua-wrapper.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +test_libs = \ + libdovecot-lua.la \ + ../lib-dovecot/libdovecot.la + +test_lua_SOURCES = test-lua.c +test_lua_CFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +test_lua_LDFLAGS = $(BINARY_LDFLAGS) +test_lua_LDADD = $(test_libs) $(LUA_LIBS) +test_lua_DEPENDENCIES = $(test_libs) +test_dict_lua_SOURCES = test-dict-lua.c +test_dict_lua_LDADD = $(test_libs) $(LUA_LIBS) +test_dict_lua_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-lua/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-lua/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 + +install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ + } + +uninstall-pkglibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ + done + +clean-pkglibLTLIBRARIES: + -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) + @list='$(pkglib_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}; \ + } + +libdovecot-lua.la: $(libdovecot_lua_la_OBJECTS) $(libdovecot_lua_la_DEPENDENCIES) $(EXTRA_libdovecot_lua_la_DEPENDENCIES) + $(AM_V_CCLD)$(libdovecot_lua_la_LINK) -rpath $(pkglibdir) $(libdovecot_lua_la_OBJECTS) $(libdovecot_lua_la_LIBADD) $(LIBS) + +test-dict-lua$(EXEEXT): $(test_dict_lua_OBJECTS) $(test_dict_lua_DEPENDENCIES) $(EXTRA_test_dict_lua_DEPENDENCIES) + @rm -f test-dict-lua$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_dict_lua_OBJECTS) $(test_dict_lua_LDADD) $(LIBS) + +test-lua$(EXEEXT): $(test_lua_OBJECTS) $(test_lua_DEPENDENCIES) $(EXTRA_test_lua_DEPENDENCIES) + @rm -f test-lua$(EXEEXT) + $(AM_V_CCLD)$(test_lua_LINK) $(test_lua_OBJECTS) $(test_lua_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-compat.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-dovecot-http.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-dovecot.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-error.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-pushstring.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-resume.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-script.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-table.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlua-thread.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-dict-lua.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lua-test-lua.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 $@ $< + +test_lua-test-lua.o: test-lua.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -MT test_lua-test-lua.o -MD -MP -MF $(DEPDIR)/test_lua-test-lua.Tpo -c -o test_lua-test-lua.o `test -f 'test-lua.c' || echo '$(srcdir)/'`test-lua.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lua-test-lua.Tpo $(DEPDIR)/test_lua-test-lua.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lua.c' object='test_lua-test-lua.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -c -o test_lua-test-lua.o `test -f 'test-lua.c' || echo '$(srcdir)/'`test-lua.c + +test_lua-test-lua.obj: test-lua.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -MT test_lua-test-lua.obj -MD -MP -MF $(DEPDIR)/test_lua-test-lua.Tpo -c -o test_lua-test-lua.obj `if test -f 'test-lua.c'; then $(CYGPATH_W) 'test-lua.c'; else $(CYGPATH_W) '$(srcdir)/test-lua.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lua-test-lua.Tpo $(DEPDIR)/test_lua-test-lua.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lua.c' object='test_lua-test-lua.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_lua_CFLAGS) $(CFLAGS) -c -o test_lua-test-lua.obj `if test -f 'test-lua.c'; then $(CYGPATH_W) 'test-lua.c'; else $(CYGPATH_W) '$(srcdir)/test-lua.c'; fi` + +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)$(pkglibdir)" "$(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-noinstPROGRAMS \ + clean-pkglibLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dlua-compat.Plo + -rm -f ./$(DEPDIR)/dlua-dovecot-http.Plo + -rm -f ./$(DEPDIR)/dlua-dovecot.Plo + -rm -f ./$(DEPDIR)/dlua-error.Plo + -rm -f ./$(DEPDIR)/dlua-pushstring.Plo + -rm -f ./$(DEPDIR)/dlua-resume.Plo + -rm -f ./$(DEPDIR)/dlua-script.Plo + -rm -f ./$(DEPDIR)/dlua-table.Plo + -rm -f ./$(DEPDIR)/dlua-thread.Plo + -rm -f ./$(DEPDIR)/test-dict-lua.Po + -rm -f ./$(DEPDIR)/test_lua-test-lua.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-pkglibLTLIBRARIES + +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)/dlua-compat.Plo + -rm -f ./$(DEPDIR)/dlua-dovecot-http.Plo + -rm -f ./$(DEPDIR)/dlua-dovecot.Plo + -rm -f ./$(DEPDIR)/dlua-error.Plo + -rm -f ./$(DEPDIR)/dlua-pushstring.Plo + -rm -f ./$(DEPDIR)/dlua-resume.Plo + -rm -f ./$(DEPDIR)/dlua-script.Plo + -rm -f ./$(DEPDIR)/dlua-table.Plo + -rm -f ./$(DEPDIR)/dlua-thread.Plo + -rm -f ./$(DEPDIR)/test-dict-lua.Po + -rm -f ./$(DEPDIR)/test_lua-test-lua.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 uninstall-pkglibLTLIBRARIES + +.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-noinstPROGRAMS clean-pkglibLTLIBRARIES 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-pkglibLTLIBRARIES 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 \ + uninstall-pkglibLTLIBRARIES + +.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-lua/dlua-compat.c b/src/lib-lua/dlua-compat.c new file mode 100644 index 0000000..c676186 --- /dev/null +++ b/src/lib-lua/dlua-compat.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "strnum.h" +#include "dlua-script-private.h" + +#if LUA_VERSION_NUM == 502 +# error "Lua 5.2 is not supported. Use Lua 5.1 or 5.3 instead." +#endif + +#ifndef HAVE_LUAL_SETFUNCS +void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) +{ + luaL_checkstack(L, nup + 1, "too many upvalues"); + for (; l->name != NULL; l++) { + int i; + lua_pushstring(L, l->name); + for (i = 0; i < nup; i++) + lua_pushvalue(L, -(nup + 1)); + lua_pushcclosure(L, l->func, nup); + lua_settable(L, -(nup + 3)); + } + lua_pop(L, nup); +} +#endif + +#ifndef HAVE_LUAL_SETMETATABLE +void luaL_setmetatable(lua_State *L, const char *tname) +{ + luaL_checkstack(L, 1, "not enough stack slots"); + luaL_getmetatable(L, tname); + lua_setmetatable(L, -2); +} +#endif + +#ifndef HAVE_LUA_ISINTEGER +# if LUA_VERSION_NUM >= 503 +# error "Lua 5.3+ should have lua_isinteger()" +# endif +/* + * Lua 5.3 added lua_isinteger() which tells us whether or not the input is + * an integer. In Lua 5.1 and 5.2, we have to emulate it. + */ +#undef lua_isinteger +int lua_isinteger(lua_State *L, int idx) +{ + int isnum; + + if (lua_type(L, idx) != LUA_TNUMBER) + return 0; + + (void) lua_tointegerx(L, idx, &isnum); + + return isnum; +} +#endif + +#ifndef HAVE_LUA_SETI +void lua_seti(lua_State *L, int index, lua_Integer n) +{ + /* stack: value (top) */ + lua_pushinteger(L, n); + /* stack: value, n (top) */ + lua_insert(L, -2); + /* stack: n, value (top) */ + + /* adjust relative stack position */ + if (index < 0) + index--; + + lua_settable(L, index); +} +#endif + +#ifndef HAVE_LUA_TOINTEGERX +# if LUA_VERSION_NUM >= 502 +# error "Lua 5.2+ should have lua_tointegerx()" +# endif +/* + * Lua 5.2 added lua_tointegerx() which tells us whether or not the + * input was an integer. In Lua 5.1, we have to emulate it to the best of + * our ability. + */ +lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum_r) +{ + lua_Integer integer; + lua_Number number; + const char *str; + + /* + * Unfortunately, Lua 5.1 doesn't provide MIN/MAX value macros for + * the lua_Integer type, so we hardcode the assumption that it is + * the same size as ptrdiff_t. This matches what Lua does by + * default. + * + * If this compile-time assertion fails, don't forget to change the + * PTRDIFF_{MIN,MAX} usage below as well. + */ + (void) COMPILE_ERROR_IF_TRUE(sizeof(lua_Integer) != sizeof(ptrdiff_t)); + + switch (lua_type(L, idx)) { + case LUA_TSTRING: + /* convert using str_to_long() */ + str = lua_tostring(L, idx); + + if (strncasecmp(str, "0x", 2) == 0) { + /* hex */ + uintmax_t tmp; + + /* skip over leading 0x */ + str += 2; + + if (str_to_uintmax_hex(str, &tmp) < 0) + break; + + *isnum_r = (tmp <= PTRDIFF_MAX) ? 1 : 0; + return tmp; + } else { + /* try decimal */ + intmax_t tmp; + + if (str_to_intmax(str, &tmp) < 0) + break; + + *isnum_r = ((tmp >= PTRDIFF_MIN) && (tmp <= PTRDIFF_MAX)) ? 1 : 0; + return tmp; + } + + break; + case LUA_TNUMBER: + /* use lua helper macro */ + number = lua_tonumber(L, idx); + + /* Lua 5.1-only macro from luaconf.h */ + lua_number2integer(integer, number); + + *isnum_r = (((lua_Number) integer) == number) ? 1 : 0; + + return integer; + default: + break; + } + + /* not an integer */ + *isnum_r = 0; + return 0; +} +#endif + +#if LUA_VERSION_NUM > 501 && LUA_VERSION_NUM < 504 +# undef lua_resume +int lua_resume_compat(lua_State *L, lua_State *from, int nargs, int *nresults) +{ + *nresults = 1; + return lua_resume(L, from, nargs); +} +#endif diff --git a/src/lib-lua/dlua-compat.h b/src/lib-lua/dlua-compat.h new file mode 100644 index 0000000..052bb83 --- /dev/null +++ b/src/lib-lua/dlua-compat.h @@ -0,0 +1,69 @@ +#ifndef DLUA_COMPAT_H +#define DLUA_COMPAT_H + +/* + * In general, make whatever Lua version we have behave more like Lua 5.3. + */ + +#if !defined(LUA_OK) +# define LUA_OK 0 +#endif + +/* functionality missing from <= 5.2 */ +#if LUA_VERSION_NUM <= 502 +# define luaL_newmetatable(L, tn) \ + ((luaL_newmetatable(L, tn) != 0) ? \ + (lua_pushstring((L), (tn)), lua_setfield((L), -2, "__name"), 1) : \ + 0) +#endif + +/* functionality missing from <= 5.1 */ +#if LUA_VERSION_NUM <= 501 +# define lua_load(L, r, s, fn, m) lua_load(L, r, s, fn) +# define luaL_newlibtable(L, l) (lua_createtable(L, 0, sizeof(l)/sizeof(*(l))-1)) +# define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_register(L, NULL, l)) +#endif + +#ifndef HAVE_LUAL_SETFUNCS +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); +#endif + +#ifndef HAVE_LUAL_SETMETATABLE +void luaL_setmetatable (lua_State *L, const char *tname); +#endif + +#ifndef HAVE_LUA_ISINTEGER +/* + * Lua 5.3 can actually keep track of intergers vs. numbers. As a + * consequence, lua_isinteger() tells us if the internal representation of + * the number is an integer (vs. a number). In previous versions, there was + * no way to check for this and our compatibility wrapper is not quite + * capable of matching the 5.3 behavior exactly. Therefore, it returns 1 + * when the number is representable as an integer instead. + */ +int lua_isinteger(lua_State *L, int idx); +#endif + +#ifndef HAVE_LUA_SETI +void lua_seti(lua_State *L, int index, lua_Integer n); +#endif + +#ifndef HAVE_LUA_TOINTEGERX +/* + * Lua 5.2 and 5.3 both have lua_tointegerx(), but their behavior is subtly + * different. Our compatibility wrapper matches the 5.3 behavior. + */ +lua_Integer lua_tointegerx(lua_State *L, int idx, int *isnum_r); +#endif + +#if LUA_VERSION_NUM > 501 && LUA_VERSION_NUM < 504 +/* + * lua_resume() compatibility function. Lua 5.4 expects an extra "nresults" + * argeument. + */ +# define lua_resume(L, from, nargs, nresults) \ + lua_resume_compat(L, from, nargs, nresults) +int lua_resume_compat(lua_State *L, lua_State *from, int nargs, int *nresults); +#endif + +#endif diff --git a/src/lib-lua/dlua-dovecot-http.c b/src/lib-lua/dlua-dovecot-http.c new file mode 100644 index 0000000..3c8c167 --- /dev/null +++ b/src/lib-lua/dlua-dovecot-http.c @@ -0,0 +1,519 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "str.h" +#include "dlua-script-private.h" +#include "http-url.h" +#include "http-client.h" +#include "http-client-private.h" +#include "istream.h" +#include "iostream-ssl.h" +#include "master-service.h" +#include "master-service-ssl-settings.h" + +#define DLUA_DOVECOT_HTTP "http" +#define DLUA_HTTP_CLIENT "struct http_client" +#define DLUA_HTTP_CLIENT_REQUEST "struct http_client_request" +#define DLUA_HTTP_RESPONSE "struct dlua_http_response" + +struct dlua_http_response { + unsigned char version_major; + unsigned char version_minor; + unsigned int status; + const char *reason; + const char *location; + string_t *payload; + time_t date, retry_after; + ARRAY_TYPE(http_header_field) headers; + pool_t pool; + const char *error; + struct event *event; +}; + +struct dlua_http_response_payload_context { + struct io *io; + struct istream *payload_istream; + string_t *payload_str; + char *error; + struct event *event; + pool_t pool; +}; + +static struct http_client_request * +dlua_check_http_request(lua_State *L, int arg) +{ + if (!lua_istable(L, arg)) { + (void)luaL_error(L, "Bad argument #%d, expected %s got %s", + arg, DLUA_HTTP_CLIENT_REQUEST, + lua_typename(L, lua_type(L, arg))); + } + lua_pushliteral(L, "item"); + lua_rawget(L, arg); + struct http_client_request **bp = lua_touserdata(L, -1); + lua_pop(L, 1); + return *bp; +} + +static int dlua_http_request_gc(lua_State *L) +{ + struct http_client_request **req = lua_touserdata(L, 1); + http_client_request_unref(req); + return 0; +} + +static int dlua_http_request_add_header(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + + struct http_client_request *req = dlua_check_http_request(L, 1); + + const char *name = luaL_checkstring(L, 2); + const char *value = luaL_checkstring(L, 3); + http_client_request_add_header(req, name, value); + return 0; +} + +static int dlua_http_request_remove_header(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + + struct http_client_request *req = dlua_check_http_request(L, 1); + + const char *name = luaL_checkstring(L, 2); + http_client_request_remove_header(req, name); + return 0; +} + +static int dlua_http_request_set_payload(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + + struct http_client_request *req = dlua_check_http_request(L, 1); + struct istream *payload_istream; + + const char *payload = luaL_checkstring(L, 2); + payload_istream = i_stream_create_copy_from_data(payload, + strlen(payload)); + http_client_request_set_payload(req, payload_istream, TRUE); + i_stream_unref(&payload_istream); + return 0; +} + +static int dlua_http_request_submit(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + + struct http_client_request *req = dlua_check_http_request(L, 1); + + /* Clear the GC hook for this request. It will be freed after it's + submitted. */ + lua_getfield(L, -1, "item"); + if (lua_getmetatable(L, -1) != 1) + return luaL_error(L, "Cound't get metatable for the request"); + lua_pushnil(L); + lua_setfield(L, -2, "__gc"); + lua_pop(L, 2); + + http_client_request_submit(req); + http_client_wait(req->client); + return 1; +} + +static luaL_Reg lua_dovecot_http_request_methods[] = { + { "add_header", dlua_http_request_add_header }, + { "remove_header", dlua_http_request_remove_header }, + { "set_payload", dlua_http_request_set_payload }, + { "submit", dlua_http_request_submit }, + { NULL, NULL } +}; + +static void dlua_push_http_request(lua_State *L, struct http_client_request *req) +{ + luaL_checkstack(L, 3, "out of memory"); + lua_createtable(L, 0, 1); + luaL_setmetatable(L, DLUA_HTTP_CLIENT_REQUEST); + + /* we need to attach gc to userdata to support older lua*/ + struct http_client_request **ptr = lua_newuserdata(L, sizeof(struct http_client_request*)); + *ptr = req; + lua_createtable(L, 0, 1); + lua_pushcfunction(L, dlua_http_request_gc); + lua_setfield(L, -2, "__gc"); + lua_setmetatable(L, -2); + lua_setfield(L, -2, "item"); + + luaL_setfuncs(L, lua_dovecot_http_request_methods, 0); +} + + +static struct http_client *dlua_check_http_client(lua_State *L, int arg) +{ + if (!lua_istable(L, arg)) { + (void)luaL_error(L, "Bad argument #%d, expected %s got %s", + arg, DLUA_HTTP_CLIENT, + lua_typename(L, lua_type(L, arg))); + } + lua_pushliteral(L, "item"); + lua_rawget(L, arg); + struct http_client **bp = lua_touserdata(L, -1); + lua_pop(L, 1); + return *bp; +} + +static int dlua_http_client_gc(lua_State *L) +{ + struct http_client **_client = lua_touserdata(L, 1); + http_client_deinit(_client); + return 0; +} +static int dlua_http_resp_gc(lua_State *L) +{ + struct dlua_http_response **_resp = lua_touserdata(L, 1); + array_free(&(*_resp)->headers); + pool_unref(&(*_resp)->pool); + return 0; +} + +static struct dlua_http_response *dlua_check_http_response(lua_State *L, int arg) +{ + if (!lua_istable(L, arg)) { + (void)luaL_error(L, "Bad argument #%d, expected %s got %s", + arg, DLUA_HTTP_RESPONSE, + lua_typename(L, lua_type(L, arg))); + } + lua_pushliteral(L, "item"); + lua_rawget(L, arg); + struct dlua_http_response **bp = lua_touserdata(L, -1); + lua_pop(L, 1); + return *bp; +} + +static int dlua_http_response_get_status(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + + const struct dlua_http_response *resp = dlua_check_http_response(L, 1); + lua_pushinteger(L, resp->status); + return 1; +} + +static int dlua_http_response_get_payload(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + + const struct dlua_http_response *resp = dlua_check_http_response(L, 1); + lua_pushlstring(L, resp->payload->data, resp->payload->used); + return 1; +} + +static int dlua_http_response_get_header(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + + const struct dlua_http_response *resp = dlua_check_http_response(L, 1); + const char *name = luaL_checkstring(L, 2); + const char *value = ""; + + const struct http_header_field *hfield; + array_foreach(&resp->headers, hfield) { + if (http_header_field_is(hfield, name)) { + value = hfield->value; + break; + } + } + + lua_pushstring(L, value); + return 1; +} + +static int dlua_http_response_get_reason(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + + const struct dlua_http_response *resp = dlua_check_http_response(L, 1); + lua_pushstring(L, resp->reason); + return 1; +} + +static const luaL_Reg dovecot_http_response_methods[] = { + { "status", dlua_http_response_get_status }, + { "payload", dlua_http_response_get_payload }, + { "header", dlua_http_response_get_header }, + { "reason", dlua_http_response_get_reason }, + { NULL, NULL } +}; + +static void +dlua_push_http_response(lua_State *L, const struct dlua_http_response *resp) +{ + luaL_checkstack(L, 3, "out of memory"); + lua_createtable(L, 0, 1); + luaL_setmetatable(L, DLUA_HTTP_RESPONSE); + + /* we need to attach gc to userdata to support older lua*/ + const struct dlua_http_response **ptr = lua_newuserdata(L, sizeof(struct dlua_http_response*)); + *ptr = resp; + lua_createtable(L, 0, 1); + lua_pushcfunction(L, dlua_http_resp_gc); + lua_setfield(L, -2, "__gc"); + lua_setmetatable(L, -2); + lua_setfield(L, -2, "item"); + + luaL_setfuncs(L, dovecot_http_response_methods, 0); +} + +static void dlua_http_response_input_payload(struct dlua_http_response_payload_context *ctx) +{ + const unsigned char *data; + size_t size; + int ret; + + /* read payload */ + while ((ret=i_stream_read_more(ctx->payload_istream, &data, &size)) > 0) { + str_append_data(ctx->payload_str, data, size); + i_stream_skip(ctx->payload_istream, size); + } + + if (ctx->payload_istream->stream_errno != 0) { + ctx->error = p_strdup_printf(ctx->pool, + "Response payload read error: %s", + i_stream_get_error(ctx->payload_istream)); + } + if (ret == 0) { + e_debug(ctx->event, "DEBUG: REQUEST: NEED MORE DATA"); + /* we will be called again for this request */ + } else { + if (ctx->payload_istream->stream_errno != 0) { + e_error(ctx->event, "ERROR: REQUEST PAYLOAD READ ERROR: %s", + i_stream_get_error(ctx->payload_istream)); + } else + e_debug(ctx->event, "DEBUG: REQUEST: Finished"); + io_remove(&ctx->io); + i_free(ctx); + } +} + +static void dlua_http_response_read_payload(const struct http_response *response, + struct dlua_http_response *dlua_resp) +{ + struct dlua_http_response_payload_context *ctx = + i_new(struct dlua_http_response_payload_context ,1); + ctx->payload_istream = response->payload; + ctx->io = io_add_istream(response->payload, + dlua_http_response_input_payload, ctx); + ctx->payload_str = dlua_resp->payload; + ctx->pool = dlua_resp->pool; + ctx->event = dlua_resp->event; + dlua_http_response_input_payload(ctx); +} + +static void +dlua_http_request_callback(const struct http_response *response, lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + + /* we need a keep a copy of http_response, otherwise the data will be + * lost when the object is freed. */ + pool_t pool = pool_alloconly_create("http_response", 1024); + struct dlua_http_response *resp = p_new(pool, struct dlua_http_response, 1); + resp->pool = pool; + resp->date = response->date; + resp->version_major = response->version_major; + resp->version_minor = response->version_minor; + resp->status = response->status; + resp->reason = p_strdup(resp->pool, response->reason); + resp->location = p_strdup(resp->pool, response->location); + resp->date = response->date; + resp->retry_after = response->retry_after; + resp->payload = str_new(resp->pool, 528); + resp->event = script->event; + p_array_init(&resp->headers, resp->pool, 2); + + const ARRAY_TYPE(http_header_field) *hdrs; + const struct http_header_field *hdr; + struct http_header_field *hdr_cpy; + + hdrs = http_response_header_get_fields(response); + if (hdrs != NULL) { + array_foreach(hdrs, hdr) { + hdr_cpy = array_append_space(&resp->headers); + hdr_cpy->name = p_strdup(resp->pool, hdr->name); + hdr_cpy->size = hdr->size; + hdr_cpy->value = p_strdup(resp->pool, hdr->value); + } + } + + if (response->payload != NULL) { + /* got payload */ + dlua_http_response_read_payload(response, resp); + } + + dlua_push_http_response(L, resp); +} + +static int dlua_http_request_new(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + + const char *url, *method = "GET"; + struct http_client_request *http_req; + struct http_url *http_url; + const char *error; + struct http_client *client = dlua_check_http_client(L, 1); + + luaL_checktype(L, 2, LUA_TTABLE); + + lua_getfield(L, -1, "url"); + if (lua_isnil(L, -1)) + return luaL_error(L, "cannot create request: url not specified"); + else + url = luaL_checkstring(L, -1); + lua_pop(L, 1); + lua_getfield(L, -1, "method"); + if (!lua_isnil(L, -1)) + method = luaL_checkstring(L, -1); + lua_pop(L, 1); + + if (http_url_parse(url, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool_datastack_create(), + &http_url, &error) < 0) { + return luaL_error(L, "Failed to parse url %s: %s", url, error); + return -1; + } + + if (http_url->have_ssl && client->set.ssl == NULL) { + return luaL_error(L, "TLS not enabled, cannot submit https request"); + } + http_req = http_client_request_url(client, method, http_url, + dlua_http_request_callback, L); + + dlua_push_http_request(L, http_req); + return 1; +} + +static const luaL_Reg dovecot_http_client_methods[] = { + { "request", dlua_http_request_new }, + { NULL, NULL } +}; + +static void dlua_push_http_client(lua_State *L, struct http_client *client) +{ + luaL_checkstack(L, 3, "out of memory"); + lua_createtable(L, 0, 1); + luaL_setmetatable(L, DLUA_HTTP_CLIENT); + + /* we need to attach gc to userdata to support older lua*/ + struct http_client **ptr = lua_newuserdata(L, sizeof(struct http_client*)); + *ptr = client; + lua_createtable(L, 0, 1); + lua_pushcfunction(L, dlua_http_client_gc); + lua_setfield(L, -2, "__gc"); + lua_setmetatable(L, -2); + lua_setfield(L, -2, "item"); + + luaL_setfuncs(L, dovecot_http_client_methods, 0); +} + +#define CLIENT_SETTING_STR(field) \ + if (dlua_table_get_string_by_str(L, -1, #field, &(set->field)) < 0) { \ + *error_r = t_strdup_printf("%s: string expected", #field); return -1; } +#define CLIENT_SETTING_UINT(field) \ + if (dlua_table_get_uint_by_str(L, -1, #field, &(set->field)) < 0) { \ + *error_r = t_strdup_printf("%s: non-negative number expected", #field); return -1; } +#define CLIENT_SETTING_BOOL(field) \ + if (dlua_table_get_bool_by_str(L, -1, #field, &(set->field)) < 0) { \ + *error_r = t_strdup_printf("%s: boolean expected", #field); return -1; } + +static int parse_client_settings(lua_State *L, struct http_client_settings *set, + const char **error_r) +{ + struct http_url *parsed_url; + const char *proxy_url; + + set->dns_client_socket_path = "dns-client"; + CLIENT_SETTING_STR(user_agent); + CLIENT_SETTING_STR(rawlog_dir); + CLIENT_SETTING_UINT(max_idle_time_msecs); +/* FIXME: Enable when asynchronous calls are supported +* CLIENT_SETTING_UINT(max_parallel_connections); +* CLIENT_SETTING_UINT(max_pipelined_requests); +*/ + CLIENT_SETTING_BOOL(no_auto_redirect); + CLIENT_SETTING_BOOL(no_auto_retry); + CLIENT_SETTING_UINT(max_redirects); + CLIENT_SETTING_UINT(max_attempts); + CLIENT_SETTING_UINT(max_connect_attempts); + CLIENT_SETTING_UINT(connect_backoff_time_msecs); + CLIENT_SETTING_UINT(connect_backoff_max_time_msecs); + CLIENT_SETTING_UINT(request_absolute_timeout_msecs); + CLIENT_SETTING_UINT(request_timeout_msecs); + CLIENT_SETTING_UINT(connect_timeout_msecs); + CLIENT_SETTING_UINT(soft_connect_timeout_msecs); + CLIENT_SETTING_UINT(max_auto_retry_delay_secs); + CLIENT_SETTING_BOOL(debug); + + if (dlua_table_get_string_by_str(L, -1, "proxy_url", &proxy_url) > 0) { + if (http_url_parse(proxy_url, NULL, HTTP_URL_ALLOW_USERINFO_PART, + pool_datastack_create(), &parsed_url, error_r) < 0) { + *error_r = t_strdup_printf("proxy_url is invalid: %s", + *error_r); + return -1; + } + set->proxy_url = parsed_url; + set->proxy_username = parsed_url->user; + set->proxy_password = parsed_url->password; + } + + lua_getfield(L, -1, "event_parent"); + if (!lua_isnil(L, -1)) + set->event_parent = dlua_check_event(L, -1); + + return 0; +} + +static int dlua_http_client_new(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + luaL_checktype(L, 1, LUA_TTABLE); + + struct http_client *client; + struct http_client_settings http_set; + struct ssl_iostream_settings ssl_set; + const char *error; + + i_zero(&http_set); + + if (parse_client_settings(L, &http_set, &error) < 0) + luaL_error(L, "Invalid HTTP client setting: %s", error); + + const struct master_service_ssl_settings *master_ssl_set = + master_service_ssl_settings_get(master_service); + master_service_ssl_client_settings_to_iostream_set(master_ssl_set, + pool_datastack_create(), &ssl_set); + http_set.ssl = &ssl_set; + + client = http_client_init(&http_set); + dlua_push_http_client(L, client); + return 1; +} + +static const luaL_Reg dovecot_http_methods[] = { + { "client", dlua_http_client_new }, + { NULL, NULL } +}; + +void dlua_dovecot_http_register(struct dlua_script *script) +{ + i_assert(script != NULL); + + lua_State *L = script->L; + + /* push dovecot table on the stack */ + dlua_get_dovecot(L); + + /* populate http methods in a table and add them as dovecot.http */ + lua_newtable(L); + luaL_setfuncs(L, dovecot_http_methods, 0); + lua_setfield(script->L, -2, DLUA_DOVECOT_HTTP); + lua_pop(script->L, 1); +} diff --git a/src/lib-lua/dlua-dovecot.c b/src/lib-lua/dlua-dovecot.c new file mode 100644 index 0000000..124f533 --- /dev/null +++ b/src/lib-lua/dlua-dovecot.c @@ -0,0 +1,681 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "str.h" +#include "dlua-script-private.h" + +#include <libgen.h> + +#define LUA_SCRIPT_DOVECOT "dovecot" +#define DLUA_EVENT_PASSTHROUGH "struct event_passthrough" +#define DLUA_EVENT "struct event" + +static void dlua_event_log(lua_State *L, struct event *event, + enum log_type log_type, const char *str); + +static void dlua_get_file_line(lua_State *L, int arg, const char **file_r, + unsigned int *line_r) +{ + const char *ptr; + lua_Debug ar; + lua_getstack(L, arg, &ar); + lua_getinfo(L, "Sl", &ar); + /* basename would be better, but basename needs memory + allocation, since it might modify the buffer contents, + so we use this which is good enough */ + if (ar.source[0] != '@') + ptr = "<non-file location>"; + else if ((ptr = strrchr(ar.source, '/')) == NULL) + ptr = ar.source; + else + ptr++; + *file_r = ptr; + *line_r = ar.currentline; +} + +static struct event_passthrough * +dlua_check_event_passthrough(lua_State *L, int arg) +{ + if (!lua_istable(L, arg)) { + (void)luaL_error(L, "Bad argument #%d, expected %s got %s", + arg, DLUA_EVENT, + lua_typename(L, lua_type(L, arg))); + } + lua_pushliteral(L, "item"); + lua_rawget(L, arg); + void *bp = (void*)lua_touserdata(L, -1); + lua_pop(L, 1); + return (struct event_passthrough*)bp; +} + +static void dlua_push_event_passthrough(lua_State *L, + struct event_passthrough *event) +{ + luaL_checkstack(L, 3, "out of memory"); + lua_createtable(L, 0, 1); + luaL_setmetatable(L, DLUA_EVENT_PASSTHROUGH); + + lua_pushlightuserdata(L, event); + lua_setfield(L, -2, "item"); +} + +static int dlua_event_pt_append_log_prefix(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *prefix = luaL_checkstring(L, 2); + + event->append_log_prefix(prefix); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_replace_log_prefix(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *prefix = luaL_checkstring(L, 2); + + event->replace_log_prefix(prefix); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_set_name(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *name = luaL_checkstring(L, 2); + + event->set_name(name); + + lua_pushvalue(L, 1); + + return 1; +} + + +static int dlua_event_pt_set_always_log_source(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + + event->set_always_log_source(); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_add_str(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *name = luaL_checkstring(L, 2); + const char *value = luaL_checkstring(L, 3); + + event->add_str(name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_add_int(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *name = luaL_checkstring(L, 2); + lua_Integer value = luaL_checkinteger(L, 3); + + event->add_int(name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_add_timeval(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *name = luaL_checkstring(L, 2); + /* this is time in seconds */ + lua_Integer value = luaL_checkinteger(L, 3); + struct timeval tv = { + .tv_sec = value, + }; + + event->add_timeval(name, &tv); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_inc_int(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *name = luaL_checkstring(L, 2); + lua_Integer value = luaL_checkinteger(L, 3); + + event->inc_int(name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_log_debug(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event->event(), LOG_TYPE_DEBUG, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_log_info(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event->event(), LOG_TYPE_INFO, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_log_warning(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event->event(), LOG_TYPE_WARNING, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_pt_log_error(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event_passthrough *event = dlua_check_event_passthrough(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event->event(), LOG_TYPE_ERROR, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static const luaL_Reg event_passthrough_methods[] ={ + { "append_log_prefix", dlua_event_pt_append_log_prefix }, + { "replace_log_prefix", dlua_event_pt_replace_log_prefix }, + { "set_always_log_source", dlua_event_pt_set_always_log_source }, + { "set_name", dlua_event_pt_set_name }, + { "add_str", dlua_event_pt_add_str }, + { "add_int", dlua_event_pt_add_int }, + { "add_timeval", dlua_event_pt_add_timeval }, + { "inc_int", dlua_event_pt_inc_int }, + { "log_debug", dlua_event_pt_log_debug }, + { "log_info", dlua_event_pt_log_info }, + { "log_warning", dlua_event_pt_log_warning }, + { "log_error", dlua_event_pt_log_error }, + { NULL, NULL } +}; + +static int dlua_event_gc(lua_State *L) +{ + struct event **event = lua_touserdata(L, 1); + event_unref(event); + return 0; +} + +struct event *dlua_check_event(lua_State *L, int arg) +{ + if (!lua_istable(L, arg)) { + (void)luaL_error(L, "Bad argument #%d, expected %s got %s", + arg, DLUA_EVENT, + lua_typename(L, lua_type(L, arg))); + } + lua_pushliteral(L, "item"); + lua_rawget(L, arg); + struct event **bp = (void*)lua_touserdata(L, -1); + lua_pop(L, 1); + return *bp; +} + +void dlua_push_event(lua_State *L, struct event *event) +{ + luaL_checkstack(L, 3, "out of memory"); + lua_createtable(L, 0, 1); + luaL_setmetatable(L, DLUA_EVENT); + + /* we need to attach gc to userdata to support older lua*/ + struct event **ptr = lua_newuserdata(L, sizeof(struct event*)); + *ptr = event; + event_ref(event); + lua_createtable(L, 0, 1); + lua_pushcfunction(L, dlua_event_gc); + lua_setfield(L, -2, "__gc"); + lua_setmetatable(L, -2); + lua_setfield(L, -2, "item"); +} + +static int dlua_event_append_log_prefix(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *prefix = luaL_checkstring(L, 2); + + event_set_append_log_prefix(event, prefix); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_replace_log_prefix(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *prefix = luaL_checkstring(L, 2); + + event_replace_log_prefix(event, prefix); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_set_name(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *name = luaL_checkstring(L, 2); + + event_set_name(event, name); + + lua_pushvalue(L, 1); + + return 1; +} + + +static int dlua_event_set_always_log_source(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + struct event *event = dlua_check_event(L, 1); + + event_set_always_log_source(event); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_add_str(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event *event = dlua_check_event(L, 1); + const char *name = luaL_checkstring(L, 2); + const char *value = luaL_checkstring(L, 3); + + event_add_str(event, name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_add_int(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event *event = dlua_check_event(L, 1); + const char *name = luaL_checkstring(L, 2); + lua_Integer value = luaL_checkinteger(L, 3); + + event_add_int(event, name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_add_timeval(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event *event = dlua_check_event(L, 1); + const char *name = luaL_checkstring(L, 2); + /* this is time in seconds */ + lua_Integer value = luaL_checkinteger(L, 3); + struct timeval tv = { + .tv_sec = value, + }; + + event_add_timeval(event, name, &tv); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_inc_int(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + struct event *event = dlua_check_event(L, 1); + const char *name = luaL_checkstring(L, 2); + lua_Integer value = luaL_checkinteger(L, 3); + + event_inc_int(event, name, value); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_log_debug(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event, LOG_TYPE_DEBUG, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_log_info(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event, LOG_TYPE_INFO, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_log_warning(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event, LOG_TYPE_WARNING, str); + + lua_pushvalue(L, 1); + + return 1; +} + +static int dlua_event_log_error(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + struct event *event = dlua_check_event(L, 1); + const char *str = luaL_checkstring(L, 2); + + dlua_event_log(L, event, LOG_TYPE_ERROR, str); + + lua_pushvalue(L, 1); + + return 1; +} + +#undef event_create_passthrough +#undef event_create +static int dlua_event_passthrough_event(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + struct event *event = dlua_check_event(L, 1); + const char *file; + unsigned int line; + + dlua_get_file_line(L, 1, &file, &line); + struct event_passthrough *e = + event_create_passthrough(event, file, line); + dlua_push_event_passthrough(L, e); + + return 1; +} + +static int dlua_event_new(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + DLUA_REQUIRE_ARGS_IN(L, 0, 1); + struct event *event, *parent = script->event; + const char *file; + unsigned int line; + + if (lua_gettop(L) == 1) + parent = dlua_check_event(L, 1); + dlua_get_file_line(L, 1, &file, &line); + event = event_create(parent, file, line); + dlua_push_event(L, event); + event_unref(&event); + return 1; +} + +static const luaL_Reg event_methods[] ={ + { "append_log_prefix", dlua_event_append_log_prefix }, + { "replace_log_prefix", dlua_event_replace_log_prefix }, + { "set_always_log_source", dlua_event_set_always_log_source }, + { "set_name", dlua_event_set_name }, + { "add_str", dlua_event_add_str }, + { "add_int", dlua_event_add_int }, + { "add_timeval", dlua_event_add_timeval }, + { "inc_int", dlua_event_inc_int }, + { "log_debug", dlua_event_log_debug }, + { "log_info", dlua_event_log_info }, + { "log_warning", dlua_event_log_warning }, + { "log_error", dlua_event_log_error }, + { "passthrough_event", dlua_event_passthrough_event }, + { NULL, NULL } +}; + +static void dlua_event_register(struct dlua_script *script){ + i_assert(script != NULL); + + luaL_newmetatable(script->L, DLUA_EVENT_PASSTHROUGH); + lua_pushvalue(script->L, -1); + lua_setfield(script->L, -2, "__index"); + luaL_setfuncs(script->L, event_passthrough_methods, 0); + lua_pop(script->L, 1); + + luaL_newmetatable(script->L, DLUA_EVENT); + lua_pushvalue(script->L, -1); + lua_setfield(script->L, -2, "__index"); + luaL_setfuncs(script->L, event_methods, 0); + lua_pop(script->L, 1); +} + +static int dlua_i_debug(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + const char *msg = luaL_checkstring(L, 1); + i_debug("%s", msg); + return 0; +} + +static int dlua_i_info(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + const char *msg = luaL_checkstring(L, 1); + i_info("%s", msg); + return 0; +} + +static int dlua_i_warning(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + const char *msg = luaL_checkstring(L, 1); + i_warning("%s", msg); + return 0; +} + +static int dlua_i_error(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + const char *msg = luaL_checkstring(L, 1); + i_error("%s", msg); + return 0; +} + +static int dlua_has_flag(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + /* we rather deal with unsigned value here */ + lua_Integer value = luaL_checkinteger(L, 1); + lua_Integer flag = luaL_checkinteger(L, 2); + + lua_pushboolean(L, (value & flag) == flag); + return 1; +} + +static int dlua_set_flag(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + lua_Integer value = luaL_checkinteger(L, 1); + lua_Integer flag = luaL_checkinteger(L, 2); + + lua_pushinteger(L, value | flag); + return 1; +} + +static int dlua_clear_flag(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + lua_Integer value = luaL_checkinteger(L, 1); + lua_Integer flag = luaL_checkinteger(L, 2); + + lua_pushinteger(L, value & (lua_Integer)~flag); + return 1; +} + +static int dlua_script_strict_index(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 2); + const char *name = luaL_checkstring(L, 2); + return luaL_error(L, "attempt to write to read undeclared global variable %s", + name); +} + +static int dlua_script_strict_newindex(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 3); + if (lua_type(L, 3) == LUA_TFUNCTION) { + /* allow defining global functions */ + lua_rawset(L, 1); + } else { + const char *name = luaL_checkstring(L, 2); + return luaL_error(L, "attempt to write to undeclared global variable %s", + name); + } + return 0; +} + +static luaL_Reg env_strict_metamethods[] = { + { "__index", dlua_script_strict_index }, + { "__newindex", dlua_script_strict_newindex }, + { NULL, NULL } +}; + +static int dlua_restrict_global_variables(lua_State *L) +{ + DLUA_REQUIRE_ARGS(L, 1); + + if (lua_toboolean(L, 1)) { + /* disable defining global variables */ + lua_getglobal(L, "_G"); + lua_newtable(L); + luaL_setfuncs(L, env_strict_metamethods, 0); + } else { + /* revert restrictions */ + lua_getglobal(L, "_G"); + lua_newtable(L); + } + lua_setmetatable(L, -2); + lua_pop(L, 1); + return 0; +} + +static luaL_Reg lua_dovecot_methods[] = { + { "i_debug", dlua_i_debug }, + { "i_info", dlua_i_info }, + { "i_warning", dlua_i_warning }, + { "i_error", dlua_i_error }, + { "event", dlua_event_new }, + { "has_flag", dlua_has_flag }, + { "set_flag", dlua_set_flag }, + { "clear_flag", dlua_clear_flag }, + { "restrict_global_variables", dlua_restrict_global_variables }, + { NULL, NULL } +}; + +void dlua_get_dovecot(lua_State *L) +{ + lua_getglobal(L, LUA_SCRIPT_DOVECOT); +} + +void dlua_dovecot_register(struct dlua_script *script) +{ + i_assert(script != NULL); + + dlua_event_register(script); + + /* Create table for holding values */ + lua_newtable(script->L); + + /* push new metatable to stack */ + luaL_newmetatable(script->L, LUA_SCRIPT_DOVECOT); + /* this will register functions to the metatable itself */ + luaL_setfuncs(script->L, lua_dovecot_methods, 0); + /* point __index to self */ + lua_pushvalue(script->L, -1); + lua_setfield(script->L, -1, "__index"); + /* set table's metatable, pops stack */ + lua_setmetatable(script->L, -2); + + /* register table as global */ + lua_setglobal(script->L, LUA_SCRIPT_DOVECOT); + + /* register http methods */ + dlua_dovecot_http_register(script); +} + +#undef event_want_level +static void dlua_event_log(lua_State *L, struct event *event, + enum log_type log_type, const char *str) +{ + struct event_log_params parms; + i_zero(&parms); + parms.log_type = log_type; + dlua_get_file_line(L, 1, &parms.source_filename, &parms.source_linenum); + if (log_type != LOG_TYPE_DEBUG || + event_want_level(event, LOG_TYPE_DEBUG, parms.source_filename, + parms.source_linenum)) { + event_log(event, &parms, "%s", str); + } else { + event_send_abort(event); + } +} diff --git a/src/lib-lua/dlua-error.c b/src/lib-lua/dlua-error.c new file mode 100644 index 0000000..90011ed --- /dev/null +++ b/src/lib-lua/dlua-error.c @@ -0,0 +1,13 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dlua-script-private.h" + +int dluaL_error(lua_State *L, const char *fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + (void)dlua_push_vfstring(L, fmt, argp); + va_end(argp); + return lua_error(L); +} diff --git a/src/lib-lua/dlua-pushstring.c b/src/lib-lua/dlua-pushstring.c new file mode 100644 index 0000000..436fd29 --- /dev/null +++ b/src/lib-lua/dlua-pushstring.c @@ -0,0 +1,26 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "dlua-script-private.h" + +const char *dlua_push_vfstring(lua_State *L, const char *fmt, va_list argp) +{ + const char *str; + T_BEGIN { + str = t_strdup_vprintf(fmt, argp); + (void)lua_pushstring(L, str); + str = lua_tostring(L, -1); + } T_END; + return str; +} + +const char *dlua_push_fstring(lua_State *L, const char *fmt, ...) +{ + const char *str; + va_list argp; + va_start(argp, fmt); + str = dlua_push_vfstring(L, fmt, argp); + va_end(argp); + return str; +} diff --git a/src/lib-lua/dlua-resume.c b/src/lib-lua/dlua-resume.c new file mode 100644 index 0000000..ac46f10 --- /dev/null +++ b/src/lib-lua/dlua-resume.c @@ -0,0 +1,208 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "dlua-script-private.h" + +#define PCALL_RESUME_STATE "pcall-resume-state" + +#define RESUME_TIMEOUT "resume-timeout" +#define RESUME_NARGS "resume-nargs" + +struct dlua_pcall_resume_state { + dlua_pcall_yieldable_callback_t *callback; + void *context; + struct timeout *to; + int status; +}; + +#ifdef DLUA_WITH_YIELDS +static void call_resume_callback(lua_State *L) +{ + struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE); + + timeout_remove(&state->to); + + dlua_tls_clear(L, PCALL_RESUME_STATE); + + state->callback(L, state->context, state->status); + + i_free(state); +} + +static void queue_resume_callback(lua_State *L, int status) +{ + struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE); + + i_assert(status != LUA_YIELD); + + if (status != LUA_OK) { + int ret; + + /* error occured: run debug.traceback() */ + + /* stack: ..., error (top) */ + lua_getglobal(L, "debug"); + + /* stack: ..., error, debug table (top) */ + lua_getfield(L, -1, "traceback"); + + /* stack: ..., error, debug table, traceback function (top) */ + lua_remove(L, -2); + + /* stack: ..., error, traceback function (top) */ + lua_pushvalue(L, -2); /* duplicate original error */ + + /* stack: ..., error, traceback function, error (top) */ + + /* + * Note that we kept the original error on the stack as well + * as passed it to debug.traceback(). The reason for that + * is that debug.traceback() itself can fail. If it fails, + * it'll generate its own error - which, ultimately, we + * don't care about. For example, consider the following + * function: + * + * function foo() + * debug.traceback = nil + * error("abc") + * end + * + * If we executed this function, it would error out - but + * it'd also cause our pcall to debug.traceback() to fail + * with "attempt to call a nil value". We want to discard + * the nil error, and just use the original ("abc"). This + * is ok because debug.traceback() simply "improves" the + * passed in error message to include a traceback and no + * traceback is better than a very mysterious error message. + */ + ret = lua_pcall(L, 1, 1, 0); + + /* stack: ..., orig error, traceback result/error (top) */ + + if (ret != LUA_OK) { + /* traceback failed, remove its error */ + lua_remove(L, -1); + } else { + /* traceback succeeded, remove original error */ + lua_remove(L, -2); + } + } + + /* + * Mangle the passed in status to match dlua_pcall(). Namely, turn + * it into -1 on error, and 0+ to indicate the number of return + * values. + */ + if (status == LUA_OK) + state->status = lua_gettop(L); + else + state->status = -1; + + i_assert(state->to == NULL); + state->to = timeout_add_short(0, call_resume_callback, L); +} + +static void dlua_pcall_yieldable_continue(lua_State *L) +{ + struct timeout *to; + int nargs, nresults; + int ret; + + nargs = dlua_tls_get_int(L, RESUME_NARGS); + to = dlua_tls_get_ptr(L, RESUME_TIMEOUT); + + timeout_remove(&to); + + dlua_tls_clear(L, RESUME_TIMEOUT); + dlua_tls_clear(L, RESUME_NARGS); + + ret = lua_resume(L, L, nargs, &nresults); + if (ret == LUA_YIELD) { + /* + * thread yielded - nothing to do + * + * We assume something will call lua_resume(). We don't + * care if it is a io related callback or just a timeout. + */ + } else if (ret == LUA_OK) { + /* thread completed - invoke callback */ + queue_resume_callback(L, ret); + } else { + /* error occurred - invoke callback */ + queue_resume_callback(L, ret); + } +} + +void dlua_pcall_yieldable_resume(lua_State *L, int nargs) +{ + struct timeout *to; + + to = timeout_add_short(0, dlua_pcall_yieldable_continue, L); + + dlua_tls_set_ptr(L, RESUME_TIMEOUT, to); + dlua_tls_set_int(L, RESUME_NARGS, nargs); +} + +/* + * Call a function with nargs arguments in a way that supports yielding. + * When the function execution completes, the passed in callback is called. + * + * Returns -1 on error or 0 on success. + */ +#undef dlua_pcall_yieldable +int dlua_pcall_yieldable(lua_State *L, const char *func_name, int nargs, + dlua_pcall_yieldable_callback_t *callback, + void *context, const char **error_r) +{ + struct dlua_pcall_resume_state *state; + int ret; + int nresults; + + i_assert(lua_status(L) == LUA_OK); + + lua_getglobal(L, func_name); + + if (!lua_isfunction(L, -1)) { + /* clean up the stack - function + arguments */ + lua_pop(L, nargs + 1); + *error_r = t_strdup_printf("'%s' is not a function", func_name); + return -1; + } + + /* allocate and stash in TLS callback state */ + state = i_new(struct dlua_pcall_resume_state, 1); + state->callback = callback; + state->context = context; + + dlua_tls_set_ptr(L, PCALL_RESUME_STATE, state); + + /* stack: args, func (top) */ + lua_insert(L, -(nargs + 1)); + + /* stack: func, args (top) */ + ret = lua_resume(L, L, nargs, &nresults); + if (ret == LUA_YIELD) { + /* + * thread yielded - nothing to do + * + * We assume something will call lua_resume(). We don't + * care if it is a io related callback or just a timeout. + */ + } else { + /* + * thread completed / errored + * + * Since there is nothing that will come back to this lua + * thread, we need to make sure the callback is called. + * + * We handle errors the same as successful completion in + * order to avoid forcing the callers to check for lua + * errors in two places - the call here and in the callback. + */ + queue_resume_callback(L, ret); + } + + return 0; +} +#endif diff --git a/src/lib-lua/dlua-script-private.h b/src/lib-lua/dlua-script-private.h new file mode 100644 index 0000000..e6c8273 --- /dev/null +++ b/src/lib-lua/dlua-script-private.h @@ -0,0 +1,264 @@ +#ifndef LUA_SCRIPT_PRIVATE_H +#define LUA_SCRIPT_PRIVATE_H 1 + +#include "dlua-script.h" +#include "lualib.h" +#include "lauxlib.h" +#include "dlua-compat.h" + +/* consistency helpers */ +#define lua_isstring(L, n) (lua_isstring((L), (n)) == 1) +#define lua_isnumber(L, n) (lua_isnumber((L), (n)) == 1) +#define lua_toboolean(L, n) (lua_toboolean((L), (n)) == 1) +#define lua_pushboolean(L, b) lua_pushboolean((L), (b) ? 1 : 0) +#define lua_isinteger(L, n) (lua_isinteger((L), (n)) == 1) + +#define DLUA_TABLE_STRING(n, val) { .name = (n),\ + .type = DLUA_TABLE_VALUE_STRING, .v.s = (val) } +#define DLUA_TABLE_INTEGER(n, val) { .name = (n), \ + .type = DLUA_TABLE_VALUE_INTEGER, .v.i = (val) } +#define DLUA_TABLE_ENUM(n) { .name = #n, \ + .type = DLUA_TABLE_VALUE_INTEGER, .v.i = (n) } +#define DLUA_TABLE_DOUBLE(n, val) { .name = (n), \ + .type = DLUA_TABLE_VALUE_DOUBLE, .v.d = (val) } +#define DLUA_TABLE_BOOLEAN(n, val) { .name = (n), \ + .type = DLUA_TABLE_VALUE_BOOLEAN, .v.b = (val) } +#define DLUA_TABLE_NULL(n, s) { .name = (n), \ + .type = DLUA_TABLE_VALUE_NULL } +#define DLUA_TABLE_END { .name = NULL } + +#define DLUA_REQUIRE_ARGS_IN(L, x, y) \ + STMT_START { \ + if (lua_gettop(L) < (x) || lua_gettop(L) > (y)) { \ + return luaL_error((L), "expected %d to %d arguments, got %d", \ + (x), (y), lua_gettop(L)); \ + } \ + } STMT_END +#define DLUA_REQUIRE_ARGS(L, x) \ + STMT_START { \ + if (lua_gettop(L) != (x)) { \ + return luaL_error((L), "expected %d arguments, got %d", \ + (x), lua_gettop(L)); \ + } \ + } STMT_END + +struct dlua_script { + struct dlua_script *prev,*next; + pool_t pool; + + lua_State *L; /* base lua context */ + + struct event *event; + const char *filename; + struct istream *in; + ssize_t last_read; + + int ref; + bool init:1; +}; + +enum dlua_table_value_type { + DLUA_TABLE_VALUE_STRING = 0, + DLUA_TABLE_VALUE_INTEGER, + DLUA_TABLE_VALUE_DOUBLE, + DLUA_TABLE_VALUE_BOOLEAN, + DLUA_TABLE_VALUE_NULL +}; + +struct dlua_table_values { + const char *name; + enum dlua_table_value_type type; + union { + const char *s; + ptrdiff_t i; + double d; + bool b; + } v; +}; + +typedef void dlua_pcall_yieldable_callback_t(lua_State *L, void *context, int status); + +extern struct event_category event_category_lua; + +/* assorted wrappers for lua_foo(), but operating on a struct dlua_script */ +void dlua_register(struct dlua_script *script, const char *name, + lua_CFunction f); + +/* Get dlua_script from lua_State */ +struct dlua_script *dlua_script_from_state(lua_State *L); + +/* register 'dovecot' global */ +void dlua_dovecot_register(struct dlua_script *script); + +/* push 'dovecot' global on top of stack */ +void dlua_get_dovecot(lua_State *L); + +/* register 'http' methods to 'dovecot' */ +void dlua_dovecot_http_register(struct dlua_script *script); + +/* assign values to table on idx */ +void dlua_set_members(lua_State *L, const struct dlua_table_values *values, int idx); + +/* push event to top of stack */ +void dlua_push_event(lua_State *L, struct event *event); + +/* get event from given stack position */ +struct event *dlua_check_event(lua_State *L, int arg); + +/* improved lua_pushfstring, can handle full C format support */ +const char *dlua_push_vfstring(lua_State *L, const char *fmt, va_list argp) ATTR_FORMAT(2, 0); +const char *dlua_push_fstring(lua_State *L, const char *fmt, ...) ATTR_FORMAT(2, 3); + +/* improved luaL_error, can handle full C format support */ +int dluaL_error(lua_State *L, const char *fmt, ...) ATTR_FORMAT(2, 3); +#define luaL_error(...) dluaL_error(__VA_ARGS__) + +/* + * Returns field from a Lua table + * + * There are different variants of these that allow for different key types + * and different value types. In general, the function name scheme is: + * + * dlua_table_get_<return type>_by_<key type> + * + * The _by_{str,int} variants use the supplied field value as the table key. + * + * The _by_thread variants use the current thread's thread object as the + * table key. + * + * Returns: + * -1 = incompatible value type + * 0 = nil or not found + * 1 = value found + */ +int dlua_table_get_luainteger_by_str(lua_State *L, int idx, const char *field, lua_Integer *value_r); +int dlua_table_get_int_by_str(lua_State *L, int idx, const char *field, int *value_r); +int dlua_table_get_intmax_by_str(lua_State *L, int idx, const char *field, intmax_t *value_r); +int dlua_table_get_uint_by_str(lua_State *L, int idx, const char *field, unsigned int *value_r); +int dlua_table_get_uintmax_by_str(lua_State *L, int idx, const char *field, uintmax_t *value_r); +int dlua_table_get_number_by_str(lua_State *L, int idx, const char *field, lua_Number *value_r); +int dlua_table_get_bool_by_str(lua_State *L, int idx, const char *field, bool *value_r); +int dlua_table_get_string_by_str(lua_State *L, int idx, const char *field, const char **value_r); +int dlua_table_get_data_by_str(lua_State *L, int idx, const char *field, const unsigned char **value_r, size_t *len_r); + +int dlua_table_get_luainteger_by_int(lua_State *L, int idx, lua_Integer field, lua_Integer *value_r); +int dlua_table_get_int_by_int(lua_State *L, int idx, lua_Integer field, int *value_r); +int dlua_table_get_intmax_by_int(lua_State *L, int idx, lua_Integer field, intmax_t *value_r); +int dlua_table_get_uint_by_int(lua_State *L, int idx, lua_Integer field, unsigned int *value_r); +int dlua_table_get_uintmax_by_int(lua_State *L, int idx, lua_Integer field, uintmax_t *value_r); +int dlua_table_get_number_by_int(lua_State *L, int idx, lua_Integer field, lua_Number *value_r); +int dlua_table_get_bool_by_int(lua_State *L, int idx, lua_Integer field, bool *value_r); +int dlua_table_get_string_by_int(lua_State *L, int idx, lua_Integer field, const char **value_r); +int dlua_table_get_data_by_int(lua_State *L, int idx, lua_Integer field, const unsigned char **value_r, size_t *len_r); + +int dlua_table_get_luainteger_by_thread(lua_State *L, int idx, lua_Integer *value_r); +int dlua_table_get_int_by_thread(lua_State *L, int idx, int *value_r); +int dlua_table_get_intmax_by_thread(lua_State *L, int idx, intmax_t *value_r); +int dlua_table_get_uint_by_thread(lua_State *L, int idx, unsigned int *value_r); +int dlua_table_get_uintmax_by_thread(lua_State *L, int idx, uintmax_t *value_r); +int dlua_table_get_number_by_thread(lua_State *L, int idx, lua_Number *value_r); +int dlua_table_get_bool_by_thread(lua_State *L, int idx, bool *value_r); +int dlua_table_get_string_by_thread(lua_State *L, int idx, const char **value_r); +int dlua_table_get_data_by_thread(lua_State *L, int idx, const unsigned char **value_r, size_t *len_r); + +/* + * Pushes onto the stack the value t[k], where t is the value at the given + * index and k is field argument. Unlike lua_gettable(), this function + * checks the type of the retrieved value against the passed in type. + * [-1,+0..1,e] + * + * There are different variants of these that allow for different key types. + * In general, the function name scheme is: + * + * dlua_table_get_by_<key type> + * + * The _by_{str,int} variants use the supplied field value as the table key. + * + * The _by_thread variants use the current thread's thread object as the + * table key. + * + * Returns: + * -1 = incompatible value type (nothing is pushed) + * 0 = nil or not found (nothing is pushed) + * 1 = value found (retrieved value is pushed to the top of the stack) + */ +int dlua_table_get_by_str(lua_State *L, int idx, int type, const char *field); +int dlua_table_get_by_int(lua_State *L, int idx, int type, lua_Integer field); +int dlua_table_get_by_thread(lua_State *L, int idx, int type); + +/* call a function in a script. + + **NOTE**: This function works differently than lua_pcall: + + return value: + -1 = error + 0+ = number of result(s) + +*/ +int dlua_pcall(lua_State *L, const char *func_name, int nargs, int nresults, + const char **error_r); + +/* dumps current stack as i_debug lines */ +void dlua_dump_stack(lua_State *L); + +/* Create new thread and keep track of it. */ +lua_State *dlua_script_new_thread(struct dlua_script *script); + +/* Close thread. */ +void dlua_script_close_thread(struct dlua_script *script, lua_State **_L); + +#ifdef DLUA_WITH_YIELDS +/* + * Call a function with nargs in a way that supports yielding. + * + * When the specified function returns, the callback will be called with the + * supplied context pointer and a status integer indicating whether an error + * occurred (-1) or whether execution completed successfully (0+). In the + * case of a successful completion, the status will indicate the number of + * results returned by the function. On failure, the top of the stack + * contains the error object. + * + * Returns: + * -1 = if function name refers to a non-function type + * 0 = function called, callback will be called in the future + */ +int dlua_pcall_yieldable(lua_State *L, const char *func_name, int nargs, + dlua_pcall_yieldable_callback_t *callback, + void *context, const char **error_r); +#define dlua_pcall_yieldable(L, func_name, nargs, callback, context, error_r) \ + dlua_pcall_yieldable(L, TRUE ? func_name : \ + CALLBACK_TYPECHECK(callback, void (*)(lua_State *, typeof(context), int)), \ + nargs, (dlua_pcall_yieldable_callback_t *)callback, context, error_r) +/* + * Resume yielded function execution. + * + * The nargs argument indicates how many items from the top of the stack + * should be "returned" by the yield. + * + * This function is to be called from other API callbacks to resume + * execution of the Lua script. For example, if a Lua script invokes a + * function to perform I/O, the function would start the async I/O and yield + * from the script. Eventually, the I/O completion callback executes, which + * would call dlua_pcall_yieldable_resume() to continue executing the Lua + * script with the supplied arguments. + * + * Note: The actual execution doesn't resume immediately. Rather, it is + * scheduled to start in the near future via a timeout. + */ +void dlua_pcall_yieldable_resume(lua_State *L, int nargs); +#endif + +/* initialize/free script's thread table */ +void dlua_init_thread_table(struct dlua_script *script); +void dlua_free_thread_table(struct dlua_script *script); + +/* thread local storage (TLS) getters & setters */ +void dlua_tls_set_ptr(lua_State *L, const char *name, void *ptr); +void *dlua_tls_get_ptr(lua_State *L, const char *name); +void dlua_tls_set_int(lua_State *L, const char *name, lua_Integer i); +lua_Integer dlua_tls_get_int(lua_State *L, const char *name); + +/* free a thread local storage (TLS) value */ +void dlua_tls_clear(lua_State *L, const char *name); + +#endif diff --git a/src/lib-lua/dlua-script.c b/src/lib-lua/dlua-script.c new file mode 100644 index 0000000..48fe286 --- /dev/null +++ b/src/lib-lua/dlua-script.c @@ -0,0 +1,453 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "istream.h" +#include "sha1.h" +#include "str.h" +#include "hex-binary.h" +#include "eacces-error.h" +#include "ioloop.h" +#include "dlua-script-private.h" + +#include <fcntl.h> +#include <unistd.h> + +/* the registry entry with a pointer to struct dlua_script */ +#define LUA_SCRIPT_REGISTRY_KEY "DLUA_SCRIPT" + +#define LUA_SCRIPT_INIT_FN "script_init" +#define LUA_SCRIPT_DEINIT_FN "script_deinit" + +struct event_category event_category_lua = { + .name = "lua", +}; + +static struct dlua_script *dlua_scripts = NULL; + +static int +dlua_script_create_finish(struct dlua_script *script, const char **error_r); + +static void *dlua_alloc(void *ctx, void *ptr, size_t osize, size_t nsize) +{ + struct dlua_script *script = + (struct dlua_script*)ctx; + + if (nsize == 0) { + p_free(script->pool, ptr); + return NULL; + } else { + return p_realloc(script->pool, ptr, osize, nsize); + } +} + +static const char *dlua_reader(lua_State *L, void *ctx, size_t *size_r) +{ + struct dlua_script *script = + (struct dlua_script*)ctx; + const unsigned char *data; + i_stream_skip(script->in, script->last_read); + if (i_stream_read_more(script->in, &data, size_r) == -1 && + script->in->stream_errno != 0) { + luaL_error(L, "read(%s) failed: %s", + script->filename, + i_stream_get_error(script->in)); + *size_r = 0; + return NULL; + } + script->last_read = *size_r; + return (const char*)data; +} + +struct dlua_script *dlua_script_from_state(lua_State *L) +{ + struct dlua_script *script; + + /* get light pointer from globals */ + lua_pushstring(L, LUA_SCRIPT_REGISTRY_KEY); + lua_gettable(L, LUA_REGISTRYINDEX); + script = lua_touserdata(L, -1); + lua_pop(L, 1); + i_assert(script != NULL); + + return script; +} + +int dlua_pcall(lua_State *L, const char *func_name, int nargs, int nresults, + const char **error_r) +{ + /* record the stack position */ + int ret = 0, debugh_idx, top = lua_gettop(L) - nargs; + + lua_getglobal(L, func_name); + + if (lua_isfunction(L, -1)) { + /* stack on entry + args + func <-- top + */ + /* move func name before arguments */ + lua_insert(L, -(nargs + 1)); + /* stack now + func + args <-- top + */ + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_replace(L, -2); + /* stack now + func + args + traceback <-- top + */ + /* move error handler before func name */ + lua_insert(L, -(nargs + 2)); + /* stack now + traceback + func + args <-- top + */ + /* record where traceback is so it's easy to get rid of even + if LUA_MULTRET is used. */ + debugh_idx = lua_gettop(L) - nargs - 1; + ret = lua_pcall(L, nargs, nresults, -(nargs + 2)); + if (ret != LUA_OK) { + *error_r = t_strdup_printf("lua_pcall(%s, %d, %d) failed: %s", + func_name, nargs, nresults, + lua_tostring(L, -1)); + /* Remove error and debug handler */ + lua_pop(L, 2); + ret = -1; + } else { + /* remove debug handler from known location */ + lua_remove(L, debugh_idx); + if (nresults == LUA_MULTRET) + nresults = lua_gettop(L) - top; + ret = nresults; + } + } else { + /* ensure stack is clean, remove function and arguments */ + lua_pop(L, nargs + 1); + *error_r = t_strdup_printf("'%s' is not a function", + func_name); + ret = -1; + } +#ifdef DEBUG + if ((ret == -1 && lua_gettop(L) != top) || + (ret >= 0 && + lua_gettop(L) != top + ret)) { + i_debug("LUA STACK UNCLEAN BEGIN for %s", func_name); + dlua_dump_stack(L); + i_debug("LUA STACK UNCLEAN END"); + } +#endif + /* enforce that stack is clean after call */ + if (ret == -1) + i_assert(lua_gettop(L) == top); + else + i_assert(ret >= 0 && lua_gettop(L) == top + ret); + return ret; +} + +static void dlua_call_deinit_function(struct dlua_script *script) +{ + const char *error; + if (!dlua_script_has_function(script, LUA_SCRIPT_DEINIT_FN)) + return; + if (dlua_pcall(script->L, LUA_SCRIPT_DEINIT_FN, 0, 0, &error) < 0) + e_error(script->event, LUA_SCRIPT_DEINIT_FN"() failed: %s", + error); +} + +int dlua_script_init(struct dlua_script *script, const char **error_r) +{ + if (script->init) + return 0; + script->init = TRUE; + + if (dlua_script_create_finish(script, error_r) < 0) + return -1; + + /* lets not fail on missing function... */ + if (!dlua_script_has_function(script, LUA_SCRIPT_INIT_FN)) + return 0; + + int ret = 0; + + if (dlua_pcall(script->L, LUA_SCRIPT_INIT_FN, 0, 1, error_r) < 0) + return -1; + + if (lua_isinteger(script->L, -1)) { + ret = lua_tointeger(script->L, -1); + if (ret != 0) { + *error_r = "Script init failed"; + ret = -1; + } + } else { + *error_r = LUA_SCRIPT_INIT_FN"() returned non-number"; + ret = -1; + } + + lua_pop(script->L, 1); + + i_assert(lua_gettop(script->L) == 0); + return ret; +} + +static int dlua_atpanic(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *error = lua_tostring(script->L, -1); + i_panic("Lua script '%s': %s", script->filename, error); +} + +static struct dlua_script *dlua_create_script(const char *name, + struct event *event_parent) +{ + pool_t pool = pool_allocfree_create(t_strdup_printf("lua script %s", name)); + struct dlua_script *script = p_new(pool, struct dlua_script, 1); + script->pool = pool; + script->filename = p_strdup(pool, name); + /* lua API says that lua_newstate will return NULL only if it's out of + memory. this cannot really happen with our allocator as it will + call i_fatal_status anyways if it runs out of memory */ + script->L = lua_newstate(dlua_alloc, script); + i_assert(script->L != NULL); + script->ref = 1; + lua_atpanic(script->L, dlua_atpanic); + luaL_openlibs(script->L); + script->event = event_create(event_parent); + event_add_str(script->event, "script", script->filename); + event_add_category(script->event, &event_category_lua); + + dlua_init_thread_table(script); + + DLLIST_PREPEND(&dlua_scripts, script); + return script; +} + +static int dlua_run_script(struct dlua_script *script, const char **error_r) +{ + /* put the error handler before script being called */ + lua_getglobal(script->L, "debug"); + lua_getfield(script->L, -1, "traceback"); + lua_replace(script->L, -2); + lua_insert(script->L, -2); + + /* we don't want anything to be returned here */ + /* stack before lua_pcall + debug.traceback + loaded script as function + */ + int err = lua_pcall(script->L, 0, 0, 1); + if (err != LUA_OK) { + *error_r = t_strdup_printf("lua_pcall(%s) failed: %s", + script->filename, + lua_tostring(script->L, -1)); + /* pop error and debug handler */ + lua_pop(script->L, 2); + err = -1; + } else { + /* pop debug handler */ + lua_pop(script->L, 1); + } + return err; +} + +static int +dlua_script_create_finish(struct dlua_script *script, const char **error_r) +{ + /* store pointer as light data to registry before calling the script */ + lua_pushstring(script->L, LUA_SCRIPT_REGISTRY_KEY); + lua_pushlightuserdata(script->L, script); + lua_settable(script->L, LUA_REGISTRYINDEX); + + if (dlua_run_script(script, error_r) < 0) + return -1; + i_assert(lua_gettop(script->L) == 0); + return 0; +} + +int dlua_script_create_string(const char *str, struct dlua_script **script_r, + struct event *event_parent, const char **error_r) +{ + struct dlua_script *script; + unsigned char scripthash[SHA1_RESULTLEN]; + const char *fn; + + *script_r = NULL; + sha1_get_digest(str, strlen(str), scripthash); + fn = binary_to_hex(scripthash, sizeof(scripthash)); + + script = dlua_create_script(fn, event_parent); + if (luaL_loadstring(script->L, str) == LUA_OK) { + *script_r = script; + return 0; + } + *error_r = t_strdup_printf("lua_load(<string>) failed: %s", + lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + dlua_script_unref(&script); + return -1; +} + +int dlua_script_create_file(const char *file, struct dlua_script **script_r, + struct event *event_parent, const char **error_r) +{ + struct dlua_script *script; + + /* lua reports file access errors poorly */ + if (access(file, O_RDONLY) < 0) { + if (errno == EACCES) + *error_r = eacces_error_get("access", file); + else + *error_r = t_strdup_printf("access(%s) failed: %m", + file); + return -1; + } + + script = dlua_create_script(file, event_parent); + if (luaL_loadfile(script->L, file) != LUA_OK) { + *error_r = t_strdup_printf("lua_load(%s) failed: %s", + file, lua_tostring(script->L, -1)); + dlua_script_unref(&script); + return -1; + } + + *script_r = script; + return 0; +} + +int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r, + struct event *event_parent, const char **error_r) +{ + struct dlua_script *script; + const char *filename = i_stream_get_name(is); + + i_assert(filename != NULL && *filename != '\0'); + + script = dlua_create_script(filename, event_parent); + script->in = is; + script->filename = p_strdup(script->pool, filename); + if (lua_load(script->L, dlua_reader, script, filename, 0) != LUA_OK) { + *error_r = t_strdup_printf("lua_load(%s) failed: %s", + filename, lua_tostring(script->L, -1)); + dlua_script_unref(&script); + return -1; + } + + *script_r = script; + return 0; +} + +static void dlua_script_destroy(struct dlua_script *script) +{ + dlua_call_deinit_function(script); + + /* close all threads */ + dlua_free_thread_table(script); + + /* close base lua */ + lua_close(script->L); + + /* remove from list */ + DLLIST_REMOVE(&dlua_scripts, script); + + event_unref(&script->event); + /* then just release memory */ + pool_unref(&script->pool); +} + +void dlua_script_ref(struct dlua_script *script) +{ + i_assert(script->ref > 0); + script->ref++; +} + +void dlua_script_unref(struct dlua_script **_script) +{ + struct dlua_script *script = *_script; + *_script = NULL; + + if (script == NULL) return; + + i_assert(script->ref > 0); + if (--script->ref > 0) + return; + + dlua_script_destroy(script); +} + +bool dlua_script_has_function(struct dlua_script *script, const char *fn) +{ + i_assert(script != NULL); + lua_getglobal(script->L, "_G"); + lua_pushstring(script->L, fn); + lua_rawget(script->L, -2); + bool ret = lua_isfunction(script->L, -1); + lua_pop(script->L, 2); + return ret; +} + +void dlua_set_members(lua_State *L, const struct dlua_table_values *values, + int idx) +{ + i_assert(L != NULL); + i_assert(lua_istable(L, idx)); + while(values->name != NULL) { + switch(values->type) { + case DLUA_TABLE_VALUE_STRING: + lua_pushstring(L, values->v.s); + break; + case DLUA_TABLE_VALUE_INTEGER: + lua_pushnumber(L, values->v.i); + break; + case DLUA_TABLE_VALUE_DOUBLE: + lua_pushnumber(L, values->v.d); + break; + case DLUA_TABLE_VALUE_BOOLEAN: + lua_pushboolean(L, values->v.b); + break; + case DLUA_TABLE_VALUE_NULL: + lua_pushnil(L); + break; + default: + i_unreached(); + } + lua_setfield(L, idx - 1, values->name); + values++; + } +} + +void dlua_dump_stack(lua_State *L) +{ + /* get everything in stack */ + int top = lua_gettop(L); + for (int i = 1; i <= top; i++) T_BEGIN { /* repeat for each level */ + int t = lua_type(L, i); + string_t *line = t_str_new(32); + str_printfa(line, "#%d: ", i); + switch (t) { + case LUA_TSTRING: /* strings */ + str_printfa(line, "`%s'", lua_tostring(L, i)); + break; + case LUA_TBOOLEAN: /* booleans */ + str_printfa(line, "`%s'", lua_toboolean(L, i) ? "true" : "false"); + break; + case LUA_TNUMBER: /* numbers */ + str_printfa(line, "%g", lua_tonumber(L, i)); + break; + default: /* other values */ + str_printfa(line, "%s", lua_typename(L, t)); + break; + } + i_debug("%s", str_c(line)); + } T_END; +} + +/* assorted wrappers */ +void dlua_register(struct dlua_script *script, const char *name, + lua_CFunction f) +{ + lua_register(script->L, name, f); +} diff --git a/src/lib-lua/dlua-script.h b/src/lib-lua/dlua-script.h new file mode 100644 index 0000000..015475b --- /dev/null +++ b/src/lib-lua/dlua-script.h @@ -0,0 +1,28 @@ +#ifndef LUA_SCRIPT_H +#define LUA_SCRIPT_H 1 + +struct dlua_script; + +/* Parse and load a lua script, without actually running it. */ +int dlua_script_create_string(const char *str, struct dlua_script **script_r, + struct event *event_parent, const char **error_r); +int dlua_script_create_file(const char *file, struct dlua_script **script_r, + struct event *event_parent, const char **error_r); +/* Remember to set script name using i_stream_set_name */ +int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r, + struct event *event_parent, const char **error_r); + +/* run dlua_script_init function */ +int dlua_script_init(struct dlua_script *script, const char **error_r); + +/* Reference lua script */ +void dlua_script_ref(struct dlua_script *script); + +/* Unreference a script, calls deinit and frees when no more + references exist */ +void dlua_script_unref(struct dlua_script **_script); + +/* see if particular function is registered */ +bool dlua_script_has_function(struct dlua_script *script, const char *fn); + +#endif diff --git a/src/lib-lua/dlua-table.c b/src/lib-lua/dlua-table.c new file mode 100644 index 0000000..8467a7e --- /dev/null +++ b/src/lib-lua/dlua-table.c @@ -0,0 +1,301 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dlua-script-private.h" + +/* + * Adjust the index by the specified delta. + * + * In a couple of places we need to adjust the passed in index to reflect + * additional items pushed onto the stack. We cannot blindly adjust the + * index because the index could be one of three things and only one of them + * is supposed to be ajusted: + * + * 1. negative number: index relative to top of stack, adjust + * 2. positive number: absolute index, don't adjust + * 3. special registry index: don't adjust + */ +static inline int adj(int idx, int delta) +{ + if ((idx == LUA_REGISTRYINDEX) || (idx > 0)) + return idx; + else + return idx - delta; +} + +/* + * Pushes onto the stack the value t[k], where t is the value at the given + * index and k is the value at the top of the stack. Unlike lua_gettable(), + * this function checks the type of the retreived value against the passed + * in type. [-1,+0..1,e] + * + * Return value: + * -1 = incompatible type + * 0 = nil or none + * 1 = found + */ +static int dlua_table_get(lua_State *L, int idx, int type) +{ + /* can only work with tables */ + if (!lua_istable(L, idx)) + return -1; + + lua_gettable(L, idx); + + /* check if the field was there */ + if (lua_isnoneornil(L, -1)) { + lua_pop(L, 1); + return 0; + } + + /* check that the field is the expected type */ + if (lua_type(L, -1) != type) { + lua_pop(L, 1); + return -1; + } + + return 1; +} + +/* Get by string name [-0,+1,e] */ +int dlua_table_get_by_str(lua_State *L, int idx, int type, const char *field) +{ + /* push the key */ + lua_pushstring(L, field); + + return dlua_table_get(L, adj(idx, 1), type); +} + +/* Get by int name [-0,+1,e] */ +int dlua_table_get_by_int(lua_State *L, int idx, int type, lua_Integer field) +{ + /* push the key */ + lua_pushinteger(L, field); + + return dlua_table_get(L, adj(idx, 1), type); +} + +/* Get by thread [-0,+1,e] */ +int dlua_table_get_by_thread(lua_State *L, int idx, int type) +{ + /* push the key */ + lua_pushthread(L); + + return dlua_table_get(L, adj(idx, 1), type); +} + +/* generate a set of functions to access fields of an integral data type */ +#define GET_INTTYPE(fxn, ctype, minval, maxval, unsigned_check) \ +int fxn##_by_str(lua_State *L, int idx, const char *field, \ + ctype *value_r) \ +{ \ + lua_Integer tmp; \ + int ret; \ + \ + ret = dlua_table_get_luainteger_by_str(L, idx, field, &tmp); \ + if (ret < 1) \ + return ret; \ + \ + if (unsigned_check) { \ + if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \ + return -1; \ + } else { \ + if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \ + return -1; \ + } \ + \ + *value_r = (ctype) tmp; \ + \ + return 1; \ +} \ +int fxn##_by_int(lua_State *L, int idx, lua_Integer field, \ + ctype *value_r) \ +{ \ + lua_Integer tmp; \ + int ret; \ + \ + ret = dlua_table_get_luainteger_by_int(L, idx, field, &tmp); \ + if (ret < 1) \ + return ret; \ + \ + if (unsigned_check) { \ + if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \ + return -1; \ + } else { \ + if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \ + return -1; \ + } \ + \ + *value_r = (ctype) tmp; \ + \ + return 1; \ +} \ +int fxn##_by_thread(lua_State *L, int idx, ctype *value_r) \ +{ \ + lua_Integer tmp; \ + int ret; \ + \ + ret = dlua_table_get_luainteger_by_thread(L, idx, &tmp); \ + if (ret < 1) \ + return ret; \ + \ + if (unsigned_check) { \ + if ((tmp < 0) || (((uintmax_t) tmp) > (maxval))) \ + return -1; \ + } else { \ + if ((tmp < (minval)) || (tmp > (intmax_t) (maxval))) \ + return -1; \ + } \ + \ + *value_r = (ctype) tmp; \ + \ + return 1; \ +} + +/* generate a set of functions to access fields of a binary data type */ +#define GET_DATAPTR(fxn) \ +int fxn##_by_str(lua_State *L, int idx, const char *field, \ + const unsigned char **value_r, size_t *len_r) \ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_str(L, idx, LUA_TSTRING, field); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \ + lua_pop(L, 1); \ + \ + return 1; \ +} \ +int fxn##_by_int(lua_State *L, int idx, lua_Integer field, \ + const unsigned char **value_r, size_t *len_r) \ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_int(L, idx, LUA_TSTRING, field); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \ + lua_pop(L, 1); \ + \ + return 1; \ +} \ +int fxn##_by_thread(lua_State *L, int idx, \ + const unsigned char **value_r, size_t *len_r) \ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_thread(L, idx, LUA_TSTRING); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = (const unsigned char *) lua_tolstring(L, -1, len_r); \ + lua_pop(L, 1); \ + \ + return 1; \ +} + +/* generate a set of functions to access fields of a generic-ish type */ +#define GET_GENERIC(fxn, ctype, ltype, cvt) \ +int fxn##_by_str(lua_State *L, int idx, const char *field, ctype *value_r)\ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_str(L, idx, (ltype), field); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = cvt(L, -1); \ + lua_pop(L, 1); \ + \ + return 1; \ +} \ +int fxn##_by_int(lua_State *L, int idx, lua_Integer field, ctype *value_r)\ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_int(L, idx, (ltype), field); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = cvt(L, -1); \ + lua_pop(L, 1); \ + \ + return 1; \ +} \ +int fxn##_by_thread(lua_State *L, int idx, ctype *value_r) \ +{ \ + int ret; \ + \ + ret = dlua_table_get_by_thread(L, idx, (ltype)); \ + if (ret < 1) \ + return ret; \ + \ + *value_r = cvt(L, -1); \ + lua_pop(L, 1); \ + \ + return 1; \ +} + +GET_INTTYPE(dlua_table_get_int, int, INT_MIN, INT_MAX, FALSE); +GET_INTTYPE(dlua_table_get_intmax, intmax_t, INTMAX_MIN, INTMAX_MAX, FALSE); +GET_INTTYPE(dlua_table_get_uint, unsigned int, 0, UINT_MAX, TRUE); +GET_INTTYPE(dlua_table_get_uintmax, uintmax_t, 0, UINTMAX_MAX, TRUE); + +/* we need to use lua_tointegerx which takes an extra argument */ +int dlua_table_get_luainteger_by_str(lua_State *L, int idx, const char *field, + lua_Integer *value_r) +{ + int isnum; + int ret; + + ret = dlua_table_get_by_str(L, idx, LUA_TNUMBER, field); + if (ret < 1) + return ret; + + *value_r = lua_tointegerx(L, -1, &isnum); + lua_pop(L, 1); + + return (isnum == 1) ? 1 : -1; +} + +/* we need to use lua_tointegerx which takes an extra argument */ +int dlua_table_get_luainteger_by_int(lua_State *L, int idx, lua_Integer field, + lua_Integer *value_r) +{ + int isnum; + int ret; + + ret = dlua_table_get_by_int(L, idx, LUA_TNUMBER, field); + if (ret < 1) + return ret; + + *value_r = lua_tointegerx(L, -1, &isnum); + lua_pop(L, 1); + + return (isnum == 1) ? 1 : -1; +} + +/* we need to use lua_tointegerx which takes an extra argument */ +int dlua_table_get_luainteger_by_thread(lua_State *L, int idx, + lua_Integer *value_r) +{ + int isnum; + int ret; + + ret = dlua_table_get_by_thread(L, idx, LUA_TNUMBER); + if (ret < 1) + return ret; + + *value_r = lua_tointegerx(L, -1, &isnum); + lua_pop(L, 1); + + return (isnum == 1) ? 1 : -1; +} + +GET_GENERIC(dlua_table_get_number, lua_Number, LUA_TNUMBER, lua_tonumber); +GET_GENERIC(dlua_table_get_bool, bool, LUA_TBOOLEAN, lua_toboolean); +GET_GENERIC(dlua_table_get_string, const char *, LUA_TSTRING, lua_tostring); +GET_DATAPTR(dlua_table_get_data); diff --git a/src/lib-lua/dlua-thread.c b/src/lib-lua/dlua-thread.c new file mode 100644 index 0000000..a0441f8 --- /dev/null +++ b/src/lib-lua/dlua-thread.c @@ -0,0 +1,276 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dlua-script-private.h" + +/* + * dlua support for threads & thread local storage (TLS) + * + * The following code keeps a table in (global) registry. This table is + * indexed by the thread objects, each mapping to another table used to hold + * thread local storage. That is: + * + * registry[thread] = {} -- threads table + * registry[thread]["foo"] = ... -- TLS value for "foo" + * + * This serves two purposes: + * + * (1) It provides TLS. + * (2) It acts as a reference to the thread object, preventing it from + * being garbage collected. + * + * The table is allocated during struct dlua_script's creation and is freed + * during the scripts destruction. Any lua threads created using + * dlua_script_new_thread() will automatically get added to this table. + */ + +/* the registry entry with a table with all the lua threads */ +#define LUA_THREAD_REGISTRY_KEY "DLUA_THREADS" + +static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L); +static void get_tls_table(lua_State *L); + +void dlua_init_thread_table(struct dlua_script *script) +{ + lua_newtable(script->L); + lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY); + + /* + * Note that we are *not* adding the main lua state since it is not + * a thread. This implies that it will not have any TLS. + */ +} + +static void warn_about_leaked_threads(struct dlua_script *script) +{ + lua_State *L = script->L; + + lua_getfield(L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY); + + i_assert(lua_type(L, -1) == LUA_TTABLE); + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + /* stack: table, thread, per-thread table */ + + /* check the key */ + if (lua_type(L, -2) != LUA_TTHREAD) { + e_error(script->event, "Unexpected %s key in thread table", + lua_typename(L, lua_type(L, -2))); + } else { + e_error(script->event, "Lua thread %p leaked", lua_tothread(L, -2)); + } + + /* check the value */ + if (lua_type(L, -1) != LUA_TTABLE) { + e_error(script->event, "Unexpected %s value in thread table", + lua_typename(L, lua_type(L, -1))); + } else { + warn_about_tls_leaks(script, L); + } + + /* pop the value for lua_next() */ + lua_pop(L, 1); + } + + lua_pop(L, 1); +} + +void dlua_free_thread_table(struct dlua_script *script) +{ + /* all threads should have been closed by now */ + warn_about_leaked_threads(script); + + /* set the thread table to nil - letting GC clean everything up */ + lua_pushnil(script->L); + lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY); +} + +lua_State *dlua_script_new_thread(struct dlua_script *script) +{ + lua_State *thread; + + /* get the threads table */ + lua_getfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY); + + /* allocate a new thread */ + thread = lua_newthread(script->L); + i_assert(thread != NULL); + + /* allocate new TLS table */ + lua_newtable(script->L); + + /* stack: threads-table, thread, TLS-table (top) */ + + /* threads-table[thread] = TLS-table */ + lua_settable(script->L, -3); + + /* pop threads table */ + lua_pop(script->L, 1); + + return thread; +} + +static void log_tls_leak(struct dlua_script *script, lua_State *L, bool full) +{ + const char *name = NULL; + + /* stack: TLS key, TLS value (top) */ + + if (full) { + lua_getmetatable(L, -1); + + if (dlua_table_get_string_by_str(L, -1, "__name", &name) < 0) + name = NULL; + + lua_pop(L, 1); /* pop the metatable */ + } + + e_error(script->event, "Lua TLS data in %p thread leaked: key '%s', " + "value %s %p (%s)", L, lua_tostring(L, -2), + full ? "userdata" : "lightuserdata", + lua_touserdata(L, -1), (name != NULL) ? name : "<no name>"); +} + +static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L) +{ + i_assert(lua_type(L, -1) == LUA_TTABLE); + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + /* stack: table, key, value (top) */ + + switch (lua_type(L, -1)) { + case LUA_TNIL: + case LUA_TNUMBER: + case LUA_TBOOLEAN: + case LUA_TSTRING: + case LUA_TFUNCTION: + case LUA_TTHREAD: + /* these are trivially freed by the Lua GC */ + break; + case LUA_TTABLE: + /* recurse into the table */ + warn_about_tls_leaks(script, L); + break; + case LUA_TUSERDATA: + log_tls_leak(script, L, TRUE); + break; + case LUA_TLIGHTUSERDATA: + log_tls_leak(script, L, FALSE); + break; + } + + /* pop the value for lua_next() */ + lua_pop(L, 1); + } +} + +void dlua_script_close_thread(struct dlua_script *script, lua_State **_L) +{ + if (*_L == NULL) + return; + + /* log any TLS userdata leaks */ + get_tls_table(*_L); + warn_about_tls_leaks(script, *_L); + lua_pop(*_L, 1); + + /* get the threads table */ + lua_getfield(*_L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY); + + /* push the thread to destroy */ + i_assert(lua_pushthread(*_L) != 1); + + lua_pushnil(*_L); + + /* stack: threads-table, thread, nil (top) */ + + /* + * threads-table[thread] = nil + * + * This assignment (1) frees all TLS for the thread, and (2) removes + * the reference to the thread saving it from GC. + */ + lua_settable(*_L, -3); + + /* pop threads table */ + lua_pop(*_L, 1); + + *_L = NULL; +} + +/* get the current thread's TLS table */ +static void get_tls_table(lua_State *L) +{ + int ret; + + /* get the threads table */ + ret = dlua_table_get_by_str(L, LUA_REGISTRYINDEX, LUA_TTABLE, + LUA_THREAD_REGISTRY_KEY); + if (ret < 1) + luaL_error(L, "lua threads table is %s", + (ret == 0) ? "missing" : "not a table"); + + /* get the TLS-table */ + ret = dlua_table_get_by_thread(L, -1, LUA_TTABLE); + if (ret < 1) + luaL_error(L, "lua TLS table for thread %p is not a table", L); + + /* stack: threads-table, TLS-table (top) */ + + /* remove threads-table from stack */ + lua_remove(L, -2); +} + +void dlua_tls_set_ptr(lua_State *L, const char *name, void *ptr) +{ + get_tls_table(L); + lua_pushlightuserdata(L, ptr); + lua_setfield(L, -2, name); + lua_pop(L, 1); +} + +void *dlua_tls_get_ptr(lua_State *L, const char *name) +{ + void *ptr; + + get_tls_table(L); + lua_getfield(L, -1, name); + + ptr = lua_touserdata(L, -1); + + lua_pop(L, 2); + + return ptr; +} + +void dlua_tls_set_int(lua_State *L, const char *name, lua_Integer i) +{ + get_tls_table(L); + lua_pushinteger(L, i); + lua_setfield(L, -2, name); + lua_pop(L, 1); +} + +lua_Integer dlua_tls_get_int(lua_State *L, const char *name) +{ + lua_Integer i; + + get_tls_table(L); + lua_getfield(L, -1, name); + + i = lua_tointeger(L, -1); + + lua_pop(L, 2); + + return i; +} + +void dlua_tls_clear(lua_State *L, const char *name) +{ + get_tls_table(L); + lua_pushnil(L); + lua_setfield(L, -2, name); + lua_pop(L, 1); +} diff --git a/src/lib-lua/dlua-wrapper.h b/src/lib-lua/dlua-wrapper.h new file mode 100644 index 0000000..3a9f459 --- /dev/null +++ b/src/lib-lua/dlua-wrapper.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef DLUA_WRAPPER_H +#define DLUA_WRAPPER_H + +/* + * The following macro generates everything necessary to wrap a C structure + * and easily push it onto Lua stacks as well as check that a value on the + * stack is of this type. + * + * To generate the necessary API, simply use the macro in your .c file. The + * arguments consist of: + * + * <typename> = the name for the structure to use in generated symbols + * <type> = the exposed structure's C type + * <putref> = the function to remove a reference from the C structure, + * called from the automatically generated __gc metamethod + * <extra_fxns_arg> = a C array of luaL_Reg structs passed to luaL_setfuncs + * to add Lua methods to the type + * + * For example, to expose struct timespec with a tostring method, one would + * use the following in a .c file: + * + * // struct timespec isn't refcounted + * static inline void timespec_putref(struct timespec *ts) + * { + * } + * + * static int timespec_tostring(lua_State *L); + * + * static const luaL_Reg timespec_fxns[] = { + * { "__tostring", timespec_tostring }, + * { NULL, NULL }, + * }; + * + * DLUA_WRAP_C_DATA(timespec, struct timespec, timespec_putref, timespec_fxns) + * + * static int timespec_tostring(lua_State *L) + * { + * struct timespec *ts; + * + * ts = xlua_timespec_getptr(L, -1, NULL); + * + * lua_pushfstring(L, "%d.%09ld", ts->tv_sec, ts->tv_nsec); + * + * return 1; + * } + * + * + * The two functions making up the exposed structure API are: + * + * static void xlua_push<typename>(lua_State *, <type> *, bool); + * static inline <type> *xlua_<typename>_getptr(lua_State *, int, bool *); + * + * The first pushes the supplied pointer onto the Lua stack, while the + * second returns the previously pushed C pointer (or generates a Lua error + * if there is a type mismatch). + * + * The push function tracks the passed in bool argument alongside the C + * pointer itself. The getptr function fills in the bool pointer (if not + * NULL) with the pushed bool value. While this bool isn't used directly by + * the generated code and therefore it can be used for anything, the + * intention is to allow the API consumers to mark certain pointers as + * "read-only" to prevent Lua scripts from attempting to mutate them. This + * allows one to push const pointers while "notifying" the methods that + * mutation of any of the members is undefined behavior. + * + * Also note that the functions are static. That is, they are intended to + * only be used in the file where they are generated since they are somewhat + * low-level functions. If some public form of a push/get function is + * desired, it is up to the API consumer to write wrappers around these and + * expose them to the rest of the codebase. + * + * Revisiting the struct timespec example above, the generated API would + * be: + * + * static void xlua_pushtimespec(lua_State *, struct timespec *, bool); + * static inline struct timespec *xlua_timespec_getptr(lua_State *, int, + * bool *); + */ +#define DLUA_WRAP_C_DATA(typename, type, putref, extra_fxns_arg) \ +struct lua_wrapper_##typename { \ + type *ptr; \ + bool ro; \ +}; \ + \ +static inline type *xlua_##typename##_getptr(lua_State *state, int idx, \ + bool *ro_r) \ +{ \ + struct lua_wrapper_##typename *wrapper; \ + \ + wrapper = luaL_checkudata(state, idx, #type); \ + \ + if (ro_r != NULL) \ + *ro_r = wrapper->ro; \ + \ + return wrapper->ptr; \ +} \ + \ +static int xlua_wrapper_##typename##_gc(lua_State *state) \ +{ \ + putref(xlua_##typename##_getptr(state, -1, NULL)); \ + \ + return 0; \ +} \ + \ +static const luaL_Reg provided_##typename##_fxns[] = { \ + { "__gc", xlua_wrapper_##typename##_gc }, \ + { NULL, NULL }, \ +}; \ + \ +/* push [-0,+1,e] */ \ +static void xlua_push##typename(lua_State *state, type *ptr, bool ro) \ +{ \ + struct lua_wrapper_##typename *wrapper; \ + \ + if (ptr == NULL) { \ + lua_pushnil(state); \ + return; \ + } \ + \ + wrapper = lua_newuserdata(state, sizeof(struct lua_wrapper_##typename)); \ + i_assert(wrapper != NULL); \ + \ + wrapper->ptr = (ptr); \ + wrapper->ro = ro; \ + \ + /* get the current metatable */ \ + luaL_getmetatable(state, #type); \ + if (lua_type(state, -1) != LUA_TTABLE) { \ + /* initialize a new metatable */ \ + const luaL_Reg *extra_fxns = (extra_fxns_arg); \ + lua_CFunction index; \ + \ + lua_pop(state, 1); \ + luaL_newmetatable(state, #type); \ + luaL_setfuncs(state, provided_##typename##_fxns, 0); \ + \ + index = NULL; \ + if (extra_fxns != NULL) { \ + unsigned i; \ + \ + luaL_setfuncs(state, extra_fxns, 0); \ + \ + for (i = 0; extra_fxns[i].name != NULL; i++) { \ + if (strcmp(extra_fxns[i].name, \ + "__index") == 0) { \ + index = extra_fxns[i].func; \ + break; \ + } \ + } \ + } \ + \ + if (index == NULL) { \ + /* set __index == metatable */ \ + lua_pushliteral(state, "__index"); \ + lua_pushvalue(state, -2); /* dup the table */ \ + lua_settable(state, -3); \ + } \ + } \ + \ + /* set the metatable */ \ + lua_setmetatable(state, -2); \ +} + +#endif diff --git a/src/lib-lua/test-dict-lua.c b/src/lib-lua/test-dict-lua.c new file mode 100644 index 0000000..66d958e --- /dev/null +++ b/src/lib-lua/test-dict-lua.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "dlua-script-private.h" +#include "dict-private.h" +#include "dict-lua.h" +#include "test-common.h" + +#include <fcntl.h> +#include <sys/stat.h> + +static void test_dict_register(void) +{ + dict_driver_register(&dict_driver_file); +} + +static void test_dict_finished(lua_State *L, struct ioloop *ioloop, int res) +{ + if (res < 0) + i_error("%s", lua_tostring(L, -1)); + io_loop_stop(ioloop); +} + +static void test_dict_lua(void) +{ + static const char *luascript = +"function test_dict(dict)\n" +" local trans = dict:transaction_begin()\n" +" trans:set('shared/testkey', 'testvalue')\n" +" trans:set('shared/testkey2', 'testvalue2')\n" +" trans:commit()\n" +"\n" +" assert(dict:lookup('shared/testkey')[1] == 'testvalue')\n" +" assert(dict:lookup('shared/testkey2')[1] == 'testvalue2')\n" +"\n" +" local key, values\n" +" local table = {}\n" +" for key, values in dict:iterate('shared/', 0) do\n" +" assert(#values == 1)\n" +" table[key] = values[1]\n" +" end\n" +" assert(table['shared/testkey'] == 'testvalue')\n" +" assert(table['shared/testkey2'] == 'testvalue2')\n" +"\n" +" trans = dict:transaction_begin()\n" +" trans:set_timestamp({['tv_sec'] = 1631278269, ['tv_nsec'] = 999999999})\n" +" trans:set('shared/testkey', 'updated')\n" +" trans:unset('shared/testkey2')\n" +" trans:commit()\n" +"\n" +" assert(dict:lookup('shared/testkey')[1] == 'updated')\n" +" assert(dict:lookup('shared/testkey2') == nil)\n" +"end\n"; + struct dict_settings set = { + .base_dir = NULL, + }; + struct dict *dict; + const char *error; + + test_begin("dict lua"); + struct ioloop *ioloop = io_loop_create(); + i_unlink_if_exists(".test.dict"); + if (dict_init("file:.test.dict", &set, &dict, &error) < 0) + i_fatal("dict_init(.test.dict) failed: %s", error); + + struct dlua_script *script; + if (dlua_script_create_string(luascript, &script, NULL, &error) < 0) + i_fatal("dlua_script_create_string() failed: %s", error); + dlua_dovecot_register(script); + if (dlua_script_init(script, &error) < 0) + i_fatal("dlua_script_init() failed: %s", error); + + lua_State *thread = dlua_script_new_thread(script); + dlua_push_dict(thread, dict); + if (dlua_pcall_yieldable(thread, "test_dict", 1, test_dict_finished, + ioloop, &error) < 0) + i_fatal("dlua_pcall() failed: %s", error); + io_loop_run(ioloop); + i_assert(lua_gettop(thread) == 0); + dlua_script_close_thread(script, &thread); + + dlua_script_unref(&script); + dict_deinit(&dict); + io_loop_destroy(&ioloop); + + i_unlink(".test.dict"); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_dict_register, + test_dict_lua, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-lua/test-lua.c b/src/lib-lua/test-lua.c new file mode 100644 index 0000000..ff5abe8 --- /dev/null +++ b/src/lib-lua/test-lua.c @@ -0,0 +1,473 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "dlua-script-private.h" + +#include <math.h> + +static int dlua_test_assert(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *what = luaL_checkstring(script->L, 1); + bool cond = lua_toboolean(script->L, 2); + + if (!cond) { + lua_Debug ar; + i_assert(lua_getinfo(L, ">Sl", &ar) == 0); + test_assert_failed(what, ar.source, ar.currentline); + } + + return 0; +} + +#define GENERATE_GETTERS(name, ctype) \ +static void check_table_get_##name##_ok(struct dlua_script *script, \ + int idx, ctype expected_value, \ + const char *str_key, \ + lua_Integer int_key) \ +{ \ + ctype value; \ + int ret; \ + \ + /* check string key */ \ + ret = dlua_table_get_##name##_by_str(script->L, idx, \ + str_key, &value); \ + test_assert(ret == 1); \ + test_assert(value == expected_value); \ + \ + /* check int key */ \ + ret = dlua_table_get_##name##_by_int(script->L, idx, \ + int_key, &value); \ + test_assert(ret == 1); \ + test_assert(value == expected_value); \ +} \ +static void check_table_get_##name##_err(struct dlua_script *script, \ + int idx, int expected_ret, \ + const char *str_key, \ + lua_Integer int_key) \ +{ \ + ctype value; \ + int ret; \ + \ + /* check string key */ \ + ret = dlua_table_get_##name##_by_str(script->L, idx, \ + str_key, &value); \ + test_assert(ret == expected_ret); \ + \ + /* check int key */ \ + ret = dlua_table_get_##name##_by_int(script->L, idx, \ + int_key, &value); \ + test_assert(ret == expected_ret); \ +} + +GENERATE_GETTERS(luainteger, lua_Integer); +GENERATE_GETTERS(int, int); +GENERATE_GETTERS(intmax, intmax_t); +GENERATE_GETTERS(uint, unsigned int); +GENERATE_GETTERS(uintmax, uintmax_t); +GENERATE_GETTERS(number, lua_Number); +GENERATE_GETTERS(bool, bool); + +/* the string comparison requires us to open-code this */ +static void check_table_get_string_ok(struct dlua_script *script, + int idx, const char *expected_value, + const char *str_key, + lua_Integer int_key) +{ + const char *value; + int ret; + + /* check string key */ + ret = dlua_table_get_string_by_str(script->L, idx, + str_key, &value); + test_assert(ret == 1); + test_assert_strcmp(value, expected_value); + + /* check int key */ + ret = dlua_table_get_string_by_int(script->L, idx, + int_key, &value); + test_assert(ret == 1); + test_assert_strcmp(value, expected_value); + + /* TODO: check thread key, which is difficult */ +} + +/* the string comparison of the _ok function requires us to open-code this */ +static void check_table_get_string_err(struct dlua_script *script, + int idx, int expected_ret, + const char *str_key, + lua_Integer int_key) +{ + const char *value; + int ret; + + /* check string key */ + ret = dlua_table_get_string_by_str(script->L, idx, + str_key, &value); + test_assert(ret == expected_ret); + + /* check int key */ + ret = dlua_table_get_string_by_int(script->L, idx, + int_key, &value); + test_assert(ret == expected_ret); + + /* TODO: check thread key, which is difficult */ +} + +static void check_table_missing(struct dlua_script *script, int idx, + const char *str_key, + lua_Integer int_key) +{ + check_table_get_luainteger_err(script, idx, 0, str_key, int_key); + check_table_get_int_err(script, idx, 0, str_key, int_key); + check_table_get_intmax_err(script, idx, 0, str_key, int_key); + check_table_get_uint_err(script, idx, 0, str_key, int_key); + check_table_get_uintmax_err(script, idx, 0, str_key, int_key); + check_table_get_number_err(script, idx, 0, str_key, int_key); + check_table_get_bool_err(script, idx, 0, str_key, int_key); + check_table_get_string_err(script, idx, 0, str_key, int_key); +} + +static void test_lua(void) +{ + static const char *luascript = +"function script_init(req)\n" +" dovecot.i_debug(\"lua script init called\")\n" +" local e = dovecot.event()\n" +" e:log_debug(\"lua script init called from event\")\n" +" return 0\n" +"end\n" +"function lua_function()\n" +"end\n" +"function lua_test_flags()\n" +" local flag = 0\n" +" flag = dovecot.set_flag(flag, 2)\n" +" flag = dovecot.set_flag(flag, 4)\n" +" flag = dovecot.set_flag(flag, 16)\n" +" test_assert(\"has_flag(flag, 8) == false\", dovecot.has_flag(flag, 8) == false)\n" +" test_assert(\"has_flag(flag, 4) == true\", dovecot.has_flag(flag, 4) == true)\n" +" flag = dovecot.clear_flag(flag, 4)\n" +" test_assert(\"has_flag(flag, 4) == false\", dovecot.has_flag(flag, 4) == false)\n" +" test_assert(\"has_flag(flag, 16) == true\", dovecot.has_flag(flag, 16) == true)\n" +"end\n" +"function lua_test_get_table()\n" +" t = {}\n" +" -- zero\n" +" t[\"zero\"] = 0\n" +" t[-2] = 0\n" +" -- small positive values\n" +" t[\"small-positive-int\"] = 1\n" +" t[-1] = 1\n" +" -- small negative values\n" +" t[\"small-negative-int\"] = -5\n" +" t[0] = -5\n" +" -- large positive float\n" +" t[\"large-positive-int\"] = 2^48\n" +" t[1] = 2^48\n" +" -- large negative float\n" +" t[\"large-negative-int\"] = -2^48\n" +" t[2] = -2^48\n" +" -- small float\n" +" t[\"small-float\"] = 1.525\n" +" t[3] = 1.525\n" +" -- bool: true\n" +" t[\"bool-true\"] = true\n" +" t[4] = true\n" +" -- bool: false\n" +" t[\"bool-false\"] = false\n" +" t[5] = false\n" +" -- string\n" +" t[\"str\"] = \"string\"\n" +" t[6] = \"string\"\n" +" return t\n" +"end\n"; + + const char *error = NULL; + struct dlua_script *script = NULL; + + test_begin("lua script"); + + test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0); + if (error != NULL) + i_fatal("dlua_script_init failed: %s", error); + + dlua_dovecot_register(script); + + dlua_register(script, "test_assert", dlua_test_assert); + + test_assert(dlua_script_init(script, &error) == 0); + test_assert(dlua_script_has_function(script, "lua_function")); + + test_assert(dlua_pcall(script->L, "lua_test_flags", 0, 0, &error) == 0); + + lua_getglobal(script->L, "lua_test_get_table"); + test_assert(lua_pcall(script->L, 0, 1, 0) == 0); + + /* + * Check table getters + */ + + /* lua_Integer */ + check_table_get_luainteger_ok(script, -1, 0, "zero", -2); + check_table_get_luainteger_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_luainteger_ok(script, -1, -5, "small-negative-int", 0); + check_table_get_luainteger_ok(script, -1, 1ll<<48, "large-positive-int", 1); + check_table_get_luainteger_ok(script, -1, -(1ll<<48), "large-negative-int", 2); + check_table_get_luainteger_err(script, -1, -1, "small-float", 3); + check_table_get_luainteger_err(script, -1, -1, "bool-true", 4); + check_table_get_luainteger_err(script, -1, -1, "bool-false", 5); + check_table_get_luainteger_err(script, -1, -1, "str", 6); + + /* int */ + check_table_get_int_ok(script, -1, 0, "zero", -2); + check_table_get_int_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_int_ok(script, -1, -5, "small-negative-int", 0); + check_table_get_int_err(script, -1, -1, "large-positive-int", 1); + check_table_get_int_err(script, -1, -1, "large-negative-int", 2); + check_table_get_int_err(script, -1, -1, "small-float", 3); + check_table_get_int_err(script, -1, -1, "bool-true", 4); + check_table_get_int_err(script, -1, -1, "bool-false", 5); + check_table_get_int_err(script, -1, -1, "str", 6); + + /* intmax_t */ + check_table_get_intmax_ok(script, -1, 0, "zero", -2); + check_table_get_intmax_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_intmax_ok(script, -1, -5, "small-negative-int", 0); + check_table_get_intmax_ok(script, -1, 1ll<<48, "large-positive-int", 1); + check_table_get_intmax_ok(script, -1, -(1ll<<48), "large-negative-int", 2); + check_table_get_intmax_err(script, -1, -1, "small-float", 3); + check_table_get_intmax_err(script, -1, -1, "bool-true", 4); + check_table_get_intmax_err(script, -1, -1, "bool-false", 5); + check_table_get_intmax_err(script, -1, -1, "str", 6); + + /* unsigned int */ + check_table_get_uint_ok(script, -1, 0, "zero", -2); + check_table_get_uint_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_uint_err(script, -1, -1, "small-negative-int", 0); + check_table_get_uint_err(script, -1, -1, "large-positive-int", 1); + check_table_get_uint_err(script, -1, -1, "large-negative-int", 2); + check_table_get_uint_err(script, -1, -1, "small-float", 3); + check_table_get_uint_err(script, -1, -1, "bool-true", 4); + check_table_get_uint_err(script, -1, -1, "bool-false", 5); + check_table_get_uint_err(script, -1, -1, "str", 6); + + /* uintmax_t */ + check_table_get_uintmax_ok(script, -1, 0, "zero", -2); + check_table_get_uintmax_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_uintmax_err(script, -1, -1, "small-negative-int", 0); + check_table_get_uintmax_ok(script, -1, 1ll<<48, "large-positive-int", 1); + check_table_get_uintmax_err(script, -1, -1, "large-negative-int", 2); + check_table_get_uintmax_err(script, -1, -1, "small-float", 3); + check_table_get_uintmax_err(script, -1, -1, "bool-true", 4); + check_table_get_uintmax_err(script, -1, -1, "bool-false", 5); + check_table_get_uintmax_err(script, -1, -1, "str", 6); + + /* lua_Number */ + check_table_get_number_ok(script, -1, 0, "zero", -2); + check_table_get_number_ok(script, -1, 1, "small-positive-int", -1); + check_table_get_number_ok(script, -1, -5, "small-negative-int", 0); + check_table_get_number_ok(script, -1, 1ll<<48, "large-positive-int", 1); + check_table_get_number_ok(script, -1, -(1ll<<48), "large-negative-int", 2); + check_table_get_number_ok(script, -1, 1.525, "small-float", 3); + check_table_get_number_err(script, -1, -1, "bool-true", 4); + check_table_get_number_err(script, -1, -1, "bool-false", 5); + check_table_get_number_err(script, -1, -1, "str", 6); + + /* bool */ + check_table_get_bool_err(script, -1, -1, "zero", -2); + check_table_get_bool_err(script, -1, -1, "small-positive-int", -1); + check_table_get_bool_err(script, -1, -1, "small-negative-int", 0); + check_table_get_bool_err(script, -1, -1, "large-positive-int", 1); + check_table_get_bool_err(script, -1, -1, "large-negative-int", 2); + check_table_get_bool_err(script, -1, -1, "small-float", 3); + check_table_get_bool_ok(script, -1, TRUE, "bool-true", 4); + check_table_get_bool_ok(script, -1, FALSE, "bool-false", 5); + check_table_get_bool_err(script, -1, -1, "str", 6); + + /* const char * */ + check_table_get_string_err(script, -1, -1, "zero", -2); + check_table_get_string_err(script, -1, -1, "small-positive-int", -1); + check_table_get_string_err(script, -1, -1, "small-negative-int", 0); + check_table_get_string_err(script, -1, -1, "large-positive-int", 1); + check_table_get_string_err(script, -1, -1, "large-negative-int", 2); + check_table_get_string_err(script, -1, -1, "small-float", 3); + check_table_get_string_err(script, -1, -1, "bool-true", 4); + check_table_get_string_err(script, -1, -1, "bool-false", 5); + check_table_get_string_ok(script, -1, "string", "str", 6); + + check_table_missing(script, -1, "missing", -10); + + lua_pop(script->L, 1); + + dlua_script_unref(&script); + + test_end(); +} + +static void test_tls(void) +{ + const char *error = NULL; + struct dlua_script *script = NULL; + lua_State *L1, *L2; + + test_begin("lua thread local storage"); + + test_assert(dlua_script_create_string("", &script, NULL, &error) == 0); + if (error != NULL) + i_fatal("dlua_script_init failed: %s", error); + + L1 = dlua_script_new_thread(script); + L2 = dlua_script_new_thread(script); + + dlua_tls_set_ptr(L1, "ptr", L1); + test_assert(dlua_tls_get_ptr(L1, "ptr") == L1); + test_assert(dlua_tls_get_ptr(L2, "ptr") == NULL); + test_assert(dlua_tls_get_int(L1, "int") == 0); + test_assert(dlua_tls_get_int(L2, "int") == 0); + + dlua_tls_set_ptr(L2, "ptr", L2); + test_assert(dlua_tls_get_ptr(L1, "ptr") == L1); + test_assert(dlua_tls_get_ptr(L2, "ptr") == L2); + test_assert(dlua_tls_get_int(L1, "int") == 0); + test_assert(dlua_tls_get_int(L2, "int") == 0); + + dlua_tls_set_int(L1, "int", 1); + dlua_tls_set_int(L2, "int", 2); + test_assert(dlua_tls_get_int(L1, "int") == 1); + test_assert(dlua_tls_get_int(L2, "int") == 2); + + dlua_tls_clear(L1, "ptr"); + test_assert(dlua_tls_get_ptr(L1, "ptr") == NULL); + test_assert(dlua_tls_get_ptr(L2, "ptr") == L2); + test_assert(dlua_tls_get_int(L1, "int") == 1); + test_assert(dlua_tls_get_int(L2, "int") == 2); + + dlua_script_close_thread(script, &L1); + + test_assert(dlua_tls_get_ptr(L2, "ptr") == L2); + test_assert(dlua_tls_get_int(L2, "int") == 2); + + dlua_tls_clear(L2, "ptr"); + dlua_script_close_thread(script, &L2); + + dlua_script_unref(&script); + + test_end(); +} + +/* check lua_tointegerx against top-of-stack item */ +static void check_tointegerx_compat(lua_State *L, bool expected_isnum, + bool expected_isint, + lua_Integer expected_value) +{ + lua_Integer value; + int isnum; + + value = lua_tointegerx(L, -1, &isnum); + test_assert((isnum == 1) == expected_isnum); + + if (isnum == 1) + test_assert(value == expected_value); + + test_assert(lua_isinteger(L, -1) == expected_isint); + + lua_pop(L, 1); +} + +static void test_compat_tointegerx_and_isinteger(void) +{ + static const struct { + const char *input; + lua_Integer output; + bool isnum; + } str_tests[] = { + { "-1", -1, TRUE }, + { "0", 0, TRUE }, + { "1", 1, TRUE }, + { "-2147483648", -2147483648, TRUE }, + { "2147483647", 2147483647, TRUE }, + { "0x123", 0x123, TRUE }, + { "0123", 123, TRUE }, /* NB: lua doesn't use leading zero for octal */ + { "0xabcdef", 0xabcdef, TRUE }, + { "0xabcdefg", 0, FALSE }, + { "abc", 0, FALSE }, + { "1.525", 0, FALSE }, + { "52.51", 0, FALSE }, + }; + static const struct { + lua_Number input; + lua_Integer output; + bool isnum; + } num_tests[] = { + { -1, -1, TRUE }, + { 0, 0, TRUE }, + { 1, 1, TRUE }, + { INT_MIN, INT_MIN, TRUE }, + { INT_MAX, INT_MAX, TRUE }, + { 1.525, 0, FALSE }, + { 52.51, 0, FALSE }, + { NAN, 0, FALSE }, + { +INFINITY, 0, FALSE }, + { -INFINITY, 0, FALSE }, + }; + static const struct { + lua_Integer input; + lua_Integer output; + } int_tests[] = { + { -1, -1 }, + { 0, 0 }, + { 1, 1 }, + { INT_MIN, INT_MIN }, + { INT_MAX, INT_MAX }, + }; + struct dlua_script *script; + const char *error; + size_t i; + + test_begin("lua compat tostringx/isinteger"); + + test_assert(dlua_script_create_string("", &script, NULL, &error) == 0); + + for (i = 0; i < N_ELEMENTS(str_tests); i++) { + lua_pushstring(script->L, str_tests[i].input); + check_tointegerx_compat(script->L, str_tests[i].isnum, FALSE, + str_tests[i].output); + } + + for (i = 0; i < N_ELEMENTS(num_tests); i++) { + bool isint; + + /* See lua_isinteger() comment in dlua-compat.h */ +#if LUA_VERSION_NUM >= 503 + isint = FALSE; +#else + isint = num_tests[i].isnum; +#endif + + lua_pushnumber(script->L, num_tests[i].input); + check_tointegerx_compat(script->L, num_tests[i].isnum, + isint, + num_tests[i].output); + } + + for (i = 0; i < N_ELEMENTS(int_tests); i++) { + lua_pushinteger(script->L, int_tests[i].input); + check_tointegerx_compat(script->L, TRUE, TRUE, + int_tests[i].output); + } + + dlua_script_unref(&script); + + test_end(); +} + +int main(void) { + void (*tests[])(void) = { + test_lua, + test_tls, + test_compat_tointegerx_and_isinteger, + NULL + }; + + return test_run(tests); +} |