summaryrefslogtreecommitdiffstats
path: root/plugins/imfile
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/imfile')
-rw-r--r--plugins/imfile/Makefile.am12
-rw-r--r--plugins/imfile/Makefile.in809
-rw-r--r--plugins/imfile/imfile.c2992
-rw-r--r--plugins/imfile/siphash.c185
4 files changed, 3998 insertions, 0 deletions
diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am
new file mode 100644
index 0000000..6bd3e82
--- /dev/null
+++ b/plugins/imfile/Makefile.am
@@ -0,0 +1,12 @@
+pkglib_LTLIBRARIES = imfile.la
+
+if OS_AIX
+LIBMATH = -lm
+else
+LIBMATH =
+endif
+
+imfile_la_SOURCES = imfile.c siphash.c
+imfile_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(LIBLOGGING_STDLOG_CFLAGS)
+imfile_la_LDFLAGS = -module -avoid-version $(LIBFASTJSON_LIBS) $(LIBMATH)
+imfile_la_LIBADD =
diff --git a/plugins/imfile/Makefile.in b/plugins/imfile/Makefile.in
new file mode 100644
index 0000000..e145e5e
--- /dev/null
+++ b/plugins/imfile/Makefile.in
@@ -0,0 +1,809 @@
+# 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@
+subdir = plugins/imfile
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_define.m4 \
+ $(top_srcdir)/m4/atomic_operations.m4 \
+ $(top_srcdir)/m4/atomic_operations_64bit.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+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)"
+LTLIBRARIES = $(pkglib_LTLIBRARIES)
+imfile_la_DEPENDENCIES =
+am_imfile_la_OBJECTS = imfile_la-imfile.lo imfile_la-siphash.lo
+imfile_la_OBJECTS = $(am_imfile_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 =
+imfile_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(imfile_la_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)/imfile_la-imfile.Plo \
+ ./$(DEPDIR)/imfile_la-siphash.Plo
+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 = $(imfile_la_SOURCES)
+DIST_SOURCES = $(imfile_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+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@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APU_CFLAGS = @APU_CFLAGS@
+APU_LIBS = @APU_LIBS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CIVETWEB_LIBS = @CIVETWEB_LIBS@
+CONF_FILE_PATH = @CONF_FILE_PATH@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CURL_CFLAGS = @CURL_CFLAGS@
+CURL_LIBS = @CURL_LIBS@
+CYGPATH_W = @CYGPATH_W@
+CZMQ_CFLAGS = @CZMQ_CFLAGS@
+CZMQ_LIBS = @CZMQ_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FAUP_LIBS = @FAUP_LIBS@
+FGREP = @FGREP@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+GSS_LIBS = @GSS_LIBS@
+GT_KSI_LS12_CFLAGS = @GT_KSI_LS12_CFLAGS@
+GT_KSI_LS12_LIBS = @GT_KSI_LS12_LIBS@
+HASH_XXHASH_LIBS = @HASH_XXHASH_LIBS@
+HIREDIS_CFLAGS = @HIREDIS_CFLAGS@
+HIREDIS_LIBS = @HIREDIS_LIBS@
+IMUDP_LIBS = @IMUDP_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IP = @IP@
+JAVA = @JAVA@
+JAVAC = @JAVAC@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBCAPNG_CFLAGS = @LIBCAPNG_CFLAGS@
+LIBCAPNG_LIBS = @LIBCAPNG_LIBS@
+LIBCAPNG_PRESENT_CFLAGS = @LIBCAPNG_PRESENT_CFLAGS@
+LIBCAPNG_PRESENT_LIBS = @LIBCAPNG_PRESENT_LIBS@
+LIBDBI_CFLAGS = @LIBDBI_CFLAGS@
+LIBDBI_LIBS = @LIBDBI_LIBS@
+LIBESTR_CFLAGS = @LIBESTR_CFLAGS@
+LIBESTR_LIBS = @LIBESTR_LIBS@
+LIBEVENT_CFLAGS = @LIBEVENT_CFLAGS@
+LIBEVENT_LIBS = @LIBEVENT_LIBS@
+LIBFASTJSON_CFLAGS = @LIBFASTJSON_CFLAGS@
+LIBFASTJSON_LIBS = @LIBFASTJSON_LIBS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBLOGGING_CFLAGS = @LIBLOGGING_CFLAGS@
+LIBLOGGING_LIBS = @LIBLOGGING_LIBS@
+LIBLOGGING_STDLOG_CFLAGS = @LIBLOGGING_STDLOG_CFLAGS@
+LIBLOGGING_STDLOG_LIBS = @LIBLOGGING_STDLOG_LIBS@
+LIBLOGNORM_CFLAGS = @LIBLOGNORM_CFLAGS@
+LIBLOGNORM_LIBS = @LIBLOGNORM_LIBS@
+LIBLZ4_CFLAGS = @LIBLZ4_CFLAGS@
+LIBLZ4_LIBS = @LIBLZ4_LIBS@
+LIBM = @LIBM@
+LIBMONGOC_CFLAGS = @LIBMONGOC_CFLAGS@
+LIBMONGOC_LIBS = @LIBMONGOC_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBRDKAFKA_CFLAGS = @LIBRDKAFKA_CFLAGS@
+LIBRDKAFKA_LIBS = @LIBRDKAFKA_LIBS@
+LIBS = @LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_JOURNAL_CFLAGS = @LIBSYSTEMD_JOURNAL_CFLAGS@
+LIBSYSTEMD_JOURNAL_LIBS = @LIBSYSTEMD_JOURNAL_LIBS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUUID_CFLAGS = @LIBUUID_CFLAGS@
+LIBUUID_LIBS = @LIBUUID_LIBS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+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@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PID_FILE_PATH = @PID_FILE_PATH@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROTON_CFLAGS = @PROTON_CFLAGS@
+PROTON_LIBS = @PROTON_LIBS@
+PROTON_PROACTOR_CFLAGS = @PROTON_PROACTOR_CFLAGS@
+PROTON_PROACTOR_LIBS = @PROTON_PROACTOR_LIBS@
+PTHREADS_CFLAGS = @PTHREADS_CFLAGS@
+PTHREADS_LIBS = @PTHREADS_LIBS@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RABBITMQ_CFLAGS = @RABBITMQ_CFLAGS@
+RABBITMQ_LIBS = @RABBITMQ_LIBS@
+RANLIB = @RANLIB@
+READLINK = @READLINK@
+REDIS = @REDIS@
+RELP_CFLAGS = @RELP_CFLAGS@
+RELP_LIBS = @RELP_LIBS@
+RSRT_CFLAGS = @RSRT_CFLAGS@
+RSRT_CFLAGS1 = @RSRT_CFLAGS1@
+RSRT_LIBS = @RSRT_LIBS@
+RSRT_LIBS1 = @RSRT_LIBS1@
+RST2MAN = @RST2MAN@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SNMP_CFLAGS = @SNMP_CFLAGS@
+SNMP_LIBS = @SNMP_LIBS@
+SOL_LIBS = @SOL_LIBS@
+STRIP = @STRIP@
+TCL_BIN_DIR = @TCL_BIN_DIR@
+TCL_INCLUDE_SPEC = @TCL_INCLUDE_SPEC@
+TCL_LIB_FILE = @TCL_LIB_FILE@
+TCL_LIB_FLAG = @TCL_LIB_FLAG@
+TCL_LIB_SPEC = @TCL_LIB_SPEC@
+TCL_PATCH_LEVEL = @TCL_PATCH_LEVEL@
+TCL_SRC_DIR = @TCL_SRC_DIR@
+TCL_STUB_LIB_FILE = @TCL_STUB_LIB_FILE@
+TCL_STUB_LIB_FLAG = @TCL_STUB_LIB_FLAG@
+TCL_STUB_LIB_SPEC = @TCL_STUB_LIB_SPEC@
+TCL_VERSION = @TCL_VERSION@
+UDPSPOOF_CFLAGS = @UDPSPOOF_CFLAGS@
+UDPSPOOF_LIBS = @UDPSPOOF_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARN_CFLAGS = @WARN_CFLAGS@
+WARN_LDFLAGS = @WARN_LDFLAGS@
+WARN_SCANNERFLAGS = @WARN_SCANNERFLAGS@
+WGET = @WGET@
+YACC = @YACC@
+YACC_FOUND = @YACC_FOUND@
+YFLAGS = @YFLAGS@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+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_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@
+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@
+moddirs = @moddirs@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+pkglib_LTLIBRARIES = imfile.la
+@OS_AIX_FALSE@LIBMATH =
+@OS_AIX_TRUE@LIBMATH = -lm
+imfile_la_SOURCES = imfile.c siphash.c
+imfile_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(LIBLOGGING_STDLOG_CFLAGS)
+imfile_la_LDFLAGS = -module -avoid-version $(LIBFASTJSON_LIBS) $(LIBMATH)
+imfile_la_LIBADD =
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(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) --gnu plugins/imfile/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu plugins/imfile/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: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+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}; \
+ }
+
+imfile.la: $(imfile_la_OBJECTS) $(imfile_la_DEPENDENCIES) $(EXTRA_imfile_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(imfile_la_LINK) -rpath $(pkglibdir) $(imfile_la_OBJECTS) $(imfile_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imfile_la-imfile.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imfile_la-siphash.Plo@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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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 $@ $<
+
+imfile_la-imfile.lo: imfile.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imfile_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imfile_la-imfile.lo -MD -MP -MF $(DEPDIR)/imfile_la-imfile.Tpo -c -o imfile_la-imfile.lo `test -f 'imfile.c' || echo '$(srcdir)/'`imfile.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imfile_la-imfile.Tpo $(DEPDIR)/imfile_la-imfile.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imfile.c' object='imfile_la-imfile.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imfile_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imfile_la-imfile.lo `test -f 'imfile.c' || echo '$(srcdir)/'`imfile.c
+
+imfile_la-siphash.lo: siphash.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imfile_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imfile_la-siphash.lo -MD -MP -MF $(DEPDIR)/imfile_la-siphash.Tpo -c -o imfile_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imfile_la-siphash.Tpo $(DEPDIR)/imfile_la-siphash.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='siphash.c' object='imfile_la-siphash.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imfile_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imfile_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+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
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibdir)"; 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-pkglibLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imfile_la-imfile.Plo
+ -rm -f ./$(DEPDIR)/imfile_la-siphash.Plo
+ -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-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)/imfile_la-imfile.Plo
+ -rm -f ./$(DEPDIR)/imfile_la-siphash.Plo
+ -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-pkglibLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool 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-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-pkglibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# 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/plugins/imfile/imfile.c b/plugins/imfile/imfile.c
new file mode 100644
index 0000000..3b0bb10
--- /dev/null
+++ b/plugins/imfile/imfile.c
@@ -0,0 +1,2992 @@
+/* imfile.c
+ *
+ * This is the input module for reading text file data. A text file is a
+ * non-binary file who's lines are delemited by the \n character.
+ *
+ * Work originally begun on 2008-02-01 by Rainer Gerhards
+ *
+ * Copyright 2008-2019 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <glob.h>
+#include <poll.h>
+#include <json.h>
+#include <fnmatch.h>
+#ifdef HAVE_SYS_INOTIFY_H
+#include <sys/inotify.h>
+#include <linux/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+#include <port.h>
+#include <sys/port.h>
+#endif
+#include "rsyslog.h" /* error codes etc... */
+#include "dirty.h"
+#include "cfsysline.h" /* access to config file objects */
+#include "module-template.h" /* generic module interface code - very important, read it! */
+#include "srUtils.h" /* some utility functions */
+#include "msg.h"
+#include "stream.h"
+#include "errmsg.h"
+#include "glbl.h"
+#include "unicode-helper.h"
+#include "prop.h"
+#include "stringbuf.h"
+#include "ruleset.h"
+#include "ratelimit.h"
+#include "srUtils.h"
+#include "parserif.h"
+#include "datetime.h"
+
+#include <regex.h>
+
+/* some platforms do not have large file support :( */
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+#ifndef HAVE_LSEEK64
+# define lseek64(fd, offset, whence) lseek(fd, offset, whence)
+#endif
+
+MODULE_TYPE_INPUT
+MODULE_TYPE_NOKEEP
+MODULE_CNFNAME("imfile")
+
+/* defines */
+#define FILE_ID_HASH_SIZE 20 /* max size of a file_id hash */
+#define FILE_ID_SIZE 512 /* how many bytes are used for file-id? */
+#define FILE_DELETE_DELAY 5 /* how many seconds to wait before finally deleting a gone file */
+
+/* Module static data */
+DEF_IMOD_STATIC_DATA /* must be present, starts static data */
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(strm)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(ruleset)
+DEFobjCurrIf(datetime)
+
+extern int rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
+ uint8_t *out, const size_t outlen); /* see siphash.c */
+
+static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */
+
+#define NUM_MULTISUB 1024 /* default max number of submits */
+#define DFLT_PollInterval 10
+#define INIT_WDMAP_TAB_SIZE 1 /* default wdMap table size - is extended as needed, use 2^x value */
+#define ADD_METADATA_UNSPECIFIED -1
+
+/* If set to 1, fileTableDisplay will be compiled and used for debugging */
+#define ULTRA_DEBUG 0
+
+/* Setting GLOB_BRACE to ZERO which disables support for GLOB_BRACE if not available on current platform */
+#ifndef GLOB_BRACE
+ #define GLOB_BRACE 0
+#endif
+
+typedef struct per_minute_rate_limit_s per_minute_rate_limit_t;
+
+struct per_minute_rate_limit_s {
+ uint64_t maxBytesPerMinute;
+ uint32_t maxLinesPerMinute;
+ uint64_t bytesThisMinute; /* bytes sent so far this minute */
+ uint32_t linesThisMinute; /* lines sent to far this minute */
+ time_t rateLimitingMinute; /* minute we are currently rate limiting for */
+};
+
+static struct configSettings_s {
+ uchar *pszFileName;
+ uchar *pszFileTag;
+ uchar *pszStateFile;
+ uchar *pszBindRuleset;
+ int iPollInterval;
+ int iPersistStateInterval; /* how often if state file to be persisted? (default 0->never) */
+ int bPersistStateAfterSubmission;/* persist state file after messages have been submitted */
+ int iFacility; /* local0 */
+ int iSeverity; /* notice, as of rfc 3164 */
+ int readMode; /* mode to use for ReadMultiLine call */
+ int64 maxLinesAtOnce; /* how many lines to process in a row? */
+ uint64_t maxBytesPerMinute; /* maximum bytes per minute to send before rate limiting */
+ uint64_t maxLinesPerMinute; /* maximum lines per minute to send before rate limiting */
+ uint32_t trimLineOverBytes; /* 0: never trim line, positive number: trim line if over bytes */
+} cs;
+
+struct instanceConf_s {
+ uchar *pszFileName;
+ uchar *pszFileName_forOldStateFile; /* we unfortunately needs this to read old state files */
+ uchar *pszDirName;
+ uchar *pszFileBaseName;
+ uchar *pszTag;
+ size_t lenTag;
+ uchar *pszStateFile;
+ uchar *pszBindRuleset;
+ int nMultiSub;
+ per_minute_rate_limit_t perMinuteRateLimits;
+ int iPersistStateInterval;
+ int bPersistStateAfterSubmission;
+ int iFacility;
+ int iSeverity;
+ int readTimeout;
+ unsigned delay_perMsg;
+ sbool bRMStateOnDel;
+ uint8_t readMode;
+ uchar *startRegex;
+ uchar *endRegex;
+ regex_t start_preg; /* compiled version of startRegex */
+ regex_t end_preg; /* compiled version of endRegex */
+ sbool discardTruncatedMsg;
+ sbool msgDiscardingError;
+ sbool escapeLF;
+ sbool reopenOnTruncate;
+ sbool addCeeTag;
+ sbool addMetadata;
+ sbool freshStartTail;
+ sbool fileNotFoundError;
+ int maxLinesAtOnce;
+ uint32_t trimLineOverBytes;
+ uint32_t ignoreOlderThan;
+ int msgFlag;
+ uchar *escapeLFString;
+ ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */
+ struct instanceConf_s *next;
+};
+
+
+/* file system objects */
+typedef struct fs_edge_s fs_edge_t;
+typedef struct fs_node_s fs_node_t;
+typedef struct act_obj_s act_obj_t;
+struct act_obj_s {
+ act_obj_t *prev;
+ act_obj_t *next;
+ fs_edge_t *edge; /* edge which this object belongs to */
+ char *name; /* full path name of active object */
+ char *basename; /* only basename */ //TODO: remove when refactoring rename support
+ char *source_name; /* if this object is target of a symlink, source_name is its name (else NULL) */
+ //char *statefile; /* base name of state file (for move operations) */
+ int wd;
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+ struct fileinfo *pfinf;
+ sbool bPortAssociated;
+ int is_deleted; /* debugging: entry deleted? */
+#endif
+ time_t timeoutBase; /* what time to calculate the timeout against? */
+ /* file dynamic data */
+ char file_id[FILE_ID_HASH_SIZE]; /* file id for this entry, once we could obtain it */
+ char file_id_prev[FILE_ID_HASH_SIZE]; /* previous file id for this entry, set if changed */
+ int in_move; /* workaround for inotify move: if set, state file must not be deleted */
+ ino_t ino; /* current inode nbr */
+ int fd; /* fd to file in order to obtain file_id (needs to be preserved across move) */
+ strm_t *pStrm; /* its stream (NULL if not assigned) */
+ int nRecords; /**< How many records did we process before persisting the stream? */
+ ratelimit_t *ratelimiter;
+ multi_submit_t multiSub;
+ int is_symlink;
+ time_t time_to_delete; /* Helper variable to DELAY the actual file delete in act_obj_unlink */
+};
+struct fs_edge_s {
+ fs_node_t *parent; /* node pointing to this edge */
+ fs_node_t *node; /* node this edge points to */
+ fs_edge_t *next;
+ uchar *name;
+ uchar *path;
+ act_obj_t *active;
+ int is_file;
+ int ninst; /* nbr of instances in instarr */
+ instanceConf_t **instarr;
+};
+struct fs_node_s {
+ fs_edge_t *edges; /* NULL in leaf nodes */
+ fs_node_t *root; /* node one level up (NULL for file system root) */
+};
+
+
+/* forward definitions */
+static rsRetVal persistStrmState(act_obj_t *);
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal);
+static rsRetVal ATTR_NONNULL(1) pollFile(act_obj_t *act);
+static int ATTR_NONNULL() getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path);
+static void ATTR_NONNULL() act_obj_unlink(act_obj_t *act);
+static uchar * ATTR_NONNULL(1, 2) getStateFileName(const act_obj_t *, uchar *, const size_t);
+static int ATTR_NONNULL() getFullStateFileName(const uchar *const, const char *const,
+ uchar *const pszout, const size_t ilenout);
+
+
+#define OPMODE_POLLING 0
+#define OPMODE_INOTIFY 1
+#define OPMODE_FEN 2
+
+/* config variables */
+struct modConfData_s {
+ rsconf_t *pConf; /* our overall config object */
+ int iPollInterval; /* number of seconds to sleep when there was no file activity */
+ int readTimeout;
+ int timeoutGranularity; /* value in ms */
+ instanceConf_t *root, *tail;
+ fs_node_t *conf_tree;
+ uint8_t opMode;
+ sbool configSetViaV2Method;
+ uchar *stateFileDirectory;
+ sbool sortFiles;
+ sbool normalizePath; /* normalize file system pathes (all start with root dir) */
+ sbool haveReadTimeouts; /* use special processing if read timeouts exist */
+ sbool bHadFileData; /* actually a global variable:
+ 1 - last call to pollFile() had data
+ 0 - last call to pollFile() had NO data
+ Must be manually reset to 0 if desired. Helper for
+ polling mode.
+ */
+};
+static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */
+static modConfData_t *runModConf = NULL;/* modConf ptr to use for run process */
+static modConfData_t *currModConf = NULL;/* modConf ptr to CURRENT mod conf (run or load) */
+
+
+#ifdef HAVE_INOTIFY_INIT
+/* We need to map watch descriptors to our actual objects. Unfortunately, the
+ * inotify API does not provide us with any cookie, so a simple O(1) algorithm
+ * cannot be done (what a shame...). We assume that maintaining the array is much
+ * less often done than looking it up, so we keep the array sorted by watch descriptor
+ * and do a binary search on the wd we get back. This is at least O(log n), which
+ * is not too bad for the anticipated use case.
+ */
+struct wd_map_s {
+ int wd; /* ascending sort key */
+ act_obj_t *act; /* point to related active object */
+};
+typedef struct wd_map_s wd_map_t;
+static wd_map_t *wdmap = NULL;
+static int nWdmap;
+static int allocMaxWdmap;
+static int ino_fd; /* fd for inotify calls */
+#endif /* #if HAVE_INOTIFY_INIT -------------------------------------------------- */
+
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+struct fileinfo {
+ struct file_obj fobj;
+ int events;
+ int port;
+};
+
+static int glport; /* Static port handle for FEN api*/
+#endif /* #if OS_SOLARIS -------------------------------------------------- */
+
+static prop_t *pInputName = NULL;
+/* there is only one global inputName for all messages generated by this input */
+
+/* module-global parameters */
+static struct cnfparamdescr modpdescr[] = {
+ { "pollinginterval", eCmdHdlrPositiveInt, 0 },
+ { "readtimeout", eCmdHdlrNonNegInt, 0 },
+ { "timeoutgranularity", eCmdHdlrPositiveInt, 0 },
+ { "sortfiles", eCmdHdlrBinary, 0 },
+ { "statefile.directory", eCmdHdlrString, 0 },
+ { "normalizepath", eCmdHdlrBinary, 0 },
+ { "mode", eCmdHdlrGetWord, 0 }
+};
+static struct cnfparamblk modpblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(modpdescr)/sizeof(struct cnfparamdescr),
+ modpdescr
+ };
+
+/* input instance parameters */
+static struct cnfparamdescr inppdescr[] = {
+ { "file", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "tag", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "severity", eCmdHdlrSeverity, 0 },
+ { "facility", eCmdHdlrFacility, 0 },
+ { "ruleset", eCmdHdlrString, 0 },
+ { "readmode", eCmdHdlrInt, 0 },
+ { "startmsg.regex", eCmdHdlrString, 0 },
+ { "endmsg.regex", eCmdHdlrString, 0 },
+ { "discardtruncatedmsg", eCmdHdlrBinary, 0 },
+ { "msgdiscardingerror", eCmdHdlrBinary, 0 },
+ { "escapelf", eCmdHdlrBinary, 0 },
+ { "escapelf.replacement", eCmdHdlrString, 0 },
+ { "reopenontruncate", eCmdHdlrBinary, 0 },
+ { "maxlinesatonce", eCmdHdlrInt, 0 },
+ { "trimlineoverbytes", eCmdHdlrInt, 0 },
+ { "maxsubmitatonce", eCmdHdlrInt, 0 },
+ { "removestateondelete", eCmdHdlrBinary, 0 },
+ { "persiststateinterval", eCmdHdlrInt, 0 },
+ { "persiststateaftersubmission", eCmdHdlrBinary, 0 },
+ { "deletestateonfiledelete", eCmdHdlrBinary, 0 },
+ { "delay.message", eCmdHdlrNonNegInt, 0 },
+ { "addmetadata", eCmdHdlrBinary, 0 },
+ { "addceetag", eCmdHdlrBinary, 0 },
+ { "statefile", eCmdHdlrString, CNFPARAM_DEPRECATED },
+ { "readtimeout", eCmdHdlrNonNegInt, 0 },
+ { "freshstarttail", eCmdHdlrBinary, 0},
+ { "filenotfounderror", eCmdHdlrBinary, 0},
+ { "needparse", eCmdHdlrBinary, 0},
+ { "ignoreolderthan", eCmdHdlrInt, 0},
+ { "maxbytesperminute", eCmdHdlrInt, 0},
+ { "maxlinesperminute", eCmdHdlrInt, 0}
+};
+static struct cnfparamblk inppblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(inppdescr)/sizeof(struct cnfparamdescr),
+ inppdescr
+ };
+
+#include "im-helper.h" /* must be included AFTER the type definitions! */
+
+
+/* Support for "old cruft" state files will potentially become optional in the
+ * future (hopefully). To prepare so, we use conditional compilation with a
+ * fixed-true condition ;-) -- rgerhards, 2018-03-28
+ * reason: https://github.com/rsyslog/rsyslog/issues/2231#issuecomment-376862280
+ */
+#define ENABLE_V1_STATE_FILE_FORMAT_SUPPORT 1
+#ifdef ENABLE_V1_STATE_FILE_FORMAT_SUPPORT
+static uchar * ATTR_NONNULL(1, 2)
+OLD_getStateFileName(const instanceConf_t *const inst,
+ uchar *const __restrict__ buf,
+ const size_t lenbuf)
+{
+ DBGPRINTF("OLD_getStateFileName trying '%s'\n", inst->pszFileName_forOldStateFile);
+ snprintf((char*)buf, lenbuf - 1, "imfile-state:%s", inst->pszFileName_forOldStateFile);
+ buf[lenbuf-1] = '\0'; /* be on the safe side... */
+ uchar *p = buf;
+ for( ; *p ; ++p) {
+ if(*p == '/')
+ *p = '-';
+ }
+ return buf;
+}
+
+
+static const uchar *
+getStateFileDir(void)
+{
+ const uchar *wrkdir;
+ assert(currModConf != NULL);
+ if(currModConf->stateFileDirectory == NULL) {
+ wrkdir = glblGetWorkDirRaw(currModConf->pConf);
+ } else {
+ wrkdir = currModConf->stateFileDirectory;
+ }
+ return(wrkdir);
+}
+
+
+/* try to open an old-style state file for given file. If the state file does not
+ * exist or cannot be read, an error is returned.
+ */
+static rsRetVal ATTR_NONNULL(1)
+OLD_openFileWithStateFile(act_obj_t *const act)
+{
+ DEFiRet;
+ strm_t *psSF = NULL;
+ uchar pszSFNam[MAXFNAME];
+ size_t lenSFNam;
+ struct stat stat_buf;
+ uchar statefile[MAXFNAME];
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+
+ uchar *const statefn = OLD_getStateFileName(inst, statefile, sizeof(statefile));
+ DBGPRINTF("OLD_openFileWithStateFile: trying to open state for '%s', state file '%s'\n",
+ act->name, statefn);
+
+ /* Get full path and file name */
+ lenSFNam = getFullStateFileName(statefn, "", pszSFNam, sizeof(pszSFNam));
+
+ /* check if the file exists */
+ if(stat((char*) pszSFNam, &stat_buf) == -1) {
+ if(errno == ENOENT) {
+ DBGPRINTF("OLD_openFileWithStateFile: NO state file (%s) exists for '%s'\n",
+ pszSFNam, act->name);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ } else {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ DBGPRINTF("OLD_openFileWithStateFile: error trying to access state "
+ "file for '%s':%s\n", act->name, errStr);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ }
+
+ /* If we reach this point, we have a state file */
+
+ DBGPRINTF("old state file found - instantiating from it\n");
+ CHKiRet(strm.Construct(&psSF));
+ CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
+ CHKiRet(strm.SetFName(psSF, pszSFNam, lenSFNam));
+ CHKiRet(strm.SetFileNotFoundError(psSF, inst->fileNotFoundError));
+ CHKiRet(strm.ConstructFinalize(psSF));
+
+ /* read back in the object */
+ CHKiRet(obj.Deserialize(&act->pStrm, (uchar*) "strm", psSF, NULL, act));
+ free(act->pStrm->pszFName);
+ CHKmalloc(act->pStrm->pszFName = ustrdup(act->name));
+
+ strm.CheckFileChange(act->pStrm);
+ CHKiRet(strm.SeekCurrOffs(act->pStrm));
+
+ /* we now persist the new state file and delete the old one, so we will
+ * never have to deal with the old one. */
+ persistStrmState(act);
+ unlink((char*)pszSFNam);
+
+finalize_it:
+ if(psSF != NULL)
+ strm.Destruct(&psSF);
+ RETiRet;
+}
+#endif /* #ifdef ENABLE_V1_STATE_FILE_FORMAT_SUPPORT */
+
+
+
+#if 0 // Code we can potentially use for new functionality // TODO: use or remove
+//TODO add a kind of portable asprintf:
+static const char * ATTR_NONNULL()
+gen_full_name(const char *const dirname, const char *const name)
+{
+ const size_t len_full_name = strlen(dirname) + 1 + strlen(name) + 1;
+ char *const full_name = malloc(len_full_name);
+ if(full_name == NULL)
+ return NULL;
+
+ snprintf(full_name, len_full_name, "%s/%s", dirname, name);
+ return full_name;
+}
+#endif
+
+
+#ifdef HAVE_INOTIFY_INIT
+#if ULTRA_DEBUG == 1
+static void
+dbg_wdmapPrint(const char *msg)
+{
+ int i;
+ DBGPRINTF("%s\n", msg);
+ for(i = 0 ; i < nWdmap ; ++i)
+ DBGPRINTF("wdmap[%d]: wd: %d, act %p, name: %s\n",
+ i, wdmap[i].wd, wdmap[i].act, wdmap[i].act->name);
+}
+#endif
+
+static rsRetVal
+wdmapInit(void)
+{
+ DEFiRet;
+ free(wdmap);
+ CHKmalloc(wdmap = malloc(sizeof(wd_map_t) * INIT_WDMAP_TAB_SIZE));
+ allocMaxWdmap = INIT_WDMAP_TAB_SIZE;
+ nWdmap = 0;
+finalize_it:
+ RETiRet;
+}
+
+
+/* note: we search backwards, as inotify tends to return increasing wd's */
+static rsRetVal
+wdmapAdd(int wd, act_obj_t *const act)
+{
+ wd_map_t *newmap;
+ int newmapsize;
+ int i;
+ DEFiRet;
+
+ for(i = nWdmap-1 ; i >= 0 && wdmap[i].wd > wd ; --i)
+ ; /* just scan */
+ if(i >= 0 && wdmap[i].wd == wd) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "imfile: wd %d already in wdmap!", wd);
+ ABORT_FINALIZE(RS_RET_FILE_ALREADY_IN_TABLE);
+ }
+ ++i;
+ /* i now points to the entry that is to be moved upwards (or end of map) */
+ if(nWdmap == allocMaxWdmap) {
+ newmapsize = 2 * allocMaxWdmap;
+ CHKmalloc(newmap = realloc(wdmap, sizeof(wd_map_t) * newmapsize));
+ // TODO: handle the error more intelligently? At all possible? -- 2013-10-15
+ wdmap = newmap;
+ allocMaxWdmap = newmapsize;
+ }
+ if(i < nWdmap) {
+ /* we need to shift to make room for new entry */
+ memmove(wdmap + i + 1, wdmap + i, sizeof(wd_map_t) * (nWdmap - i));
+ }
+ wdmap[i].wd = wd;
+ wdmap[i].act = act;
+ ++nWdmap;
+ DBGPRINTF("add wdmap[%d]: wd %d, act obj %p, path %s\n", i, wd, act, act->name);
+
+finalize_it:
+ RETiRet;
+}
+
+/* return wd or -1 on error */
+static int
+in_setupWatch(act_obj_t *const act, const int is_file)
+{
+ int wd = -1;
+ if(runModConf->opMode != OPMODE_INOTIFY)
+ goto done;
+
+ wd = inotify_add_watch(ino_fd, act->name,
+ (is_file) ? IN_MODIFY|IN_DONT_FOLLOW : IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO);
+ if(wd < 0) {
+ if (errno == EACCES) { /* There is high probability of selinux denial on top-level paths */
+ DBGPRINTF("imfile: permission denied when adding watch for '%s'\n", act->name);
+ } else {
+ LogError(errno, RS_RET_IO_ERROR, "imfile: cannot watch object '%s'", act->name);
+ }
+ goto done;
+ }
+ wdmapAdd(wd, act);
+ DBGPRINTF("in_setupWatch: watch %d added for %s(object %p)\n", wd, act->name, act);
+done: return wd;
+}
+
+/* compare function for bsearch() */
+static int
+wdmap_cmp(const void *k, const void *a)
+{
+ int key = *((int*) k);
+ wd_map_t *etry = (wd_map_t*) a;
+ if(key < etry->wd)
+ return -1;
+ else if(key > etry->wd)
+ return 1;
+ else
+ return 0;
+}
+/* looks up a wdmap entry and returns it's index if found
+ * or -1 if not found.
+ */
+static wd_map_t *
+wdmapLookup(int wd)
+{
+ return bsearch(&wd, wdmap, nWdmap, sizeof(wd_map_t), wdmap_cmp);
+}
+
+
+static rsRetVal
+wdmapDel(const int wd)
+{
+ int i;
+ DEFiRet;
+
+ for(i = 0 ; i < nWdmap && wdmap[i].wd < wd ; ++i)
+ ; /* just scan */
+ if(i == nWdmap || wdmap[i].wd != wd) {
+ DBGPRINTF("wd %d shall be deleted but not in wdmap!\n", wd);
+ FINALIZE;
+ }
+
+ if(i < nWdmap-1) {
+ /* we need to shift to delete it (see comment at wdmap definition) */
+ memmove(wdmap + i, wdmap + i + 1, sizeof(wd_map_t) * (nWdmap - i - 1));
+ }
+ --nWdmap;
+ DBGPRINTF("wd %d deleted, was idx %d\n", wd, i);
+
+finalize_it:
+ RETiRet;
+}
+
+#endif // #ifdef HAVE_INOTIFY_INIT
+
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+static void ATTR_NONNULL()
+fen_setupWatch(act_obj_t *const act)
+{
+ DBGPRINTF("fen_setupWatch: enter, opMode %d\n", runModConf->opMode);
+ if(runModConf->opMode != OPMODE_FEN)
+ goto done;
+
+ DBGPRINTF("fen_setupWatch: %s\n", act->name);
+ if(act->pfinf == NULL) {
+ act->pfinf = malloc(sizeof(struct fileinfo));
+ if (act->pfinf == NULL) {
+ LogError(errno, RS_RET_OUT_OF_MEMORY, "imfile: fen_setupWatch alloc memory "
+ "for fileinfo failed ");
+ goto done;
+ }
+ if ((act->pfinf->fobj.fo_name = strdup(act->name)) == NULL) {
+ LogError(errno, RS_RET_OUT_OF_MEMORY, "imfile: fen_setupWatch alloc memory "
+ "for strdup failed ");
+ free(act->pfinf);
+ act->pfinf = NULL;
+ goto done;
+ }
+ act->pfinf->events = FILE_MODIFIED;
+ act->pfinf->port = glport;
+ act->bPortAssociated = 0;
+ }
+
+ DBGPRINTF("fen_setupWatch: bPortAssociated %d\n", act->bPortAssociated);
+ if(act->bPortAssociated) {
+ goto done;
+ }
+
+ struct stat fileInfo;
+ const int r = stat(act->name, &fileInfo);
+ if(r == -1) { /* object gone away? */
+ DBGPRINTF("fen_setupWatch: file gone away, no watch: '%s'\n", act->name);
+ goto done;
+ }
+
+ /* note: FEN watch must be re-registered each time - this is what we do now */
+ act->pfinf->fobj.fo_atime = fileInfo.st_atim;
+ act->pfinf->fobj.fo_mtime = fileInfo.st_mtim;
+ act->pfinf->fobj.fo_ctime = fileInfo.st_ctim;
+ if(port_associate(glport, PORT_SOURCE_FILE, (uintptr_t)&(act->pfinf->fobj),
+ act->pfinf->events, (void *)act) == -1) {
+ LogError(errno, RS_RET_SYS_ERR, "fen_setupWatch: Failed to associate port for file "
+ ": %s\n", act->pfinf->fobj.fo_name);
+ goto done;
+ } else {
+ /* Port successfull listening now*/
+ DBGPRINTF("fen_setupWatch: associated port for file %s\n", act->name);
+ act->bPortAssociated = 1;
+ }
+
+ DBGPRINTF("in_setupWatch: fen association added for %s\n", act->name);
+done: return;
+}
+#else
+static void ATTR_NONNULL()
+fen_setupWatch(act_obj_t *const act __attribute__((unused)))
+{
+ DBGPRINTF("fen_setupWatch: DUMMY CALLED - not on Solaris?\n");
+}
+#endif /* FEN */
+
+static void
+fs_node_print(const fs_node_t *const node, const int level)
+{
+ fs_edge_t *chld;
+ act_obj_t *act;
+ dbgprintf("node print[%2.2d]: %p edges:\n", level, node);
+
+ for(chld = node->edges ; chld != NULL ; chld = chld->next) {
+ dbgprintf("node print[%2.2d]: child %p '%s' isFile %d, path: '%s'\n",
+ level, chld->node, chld->name, chld->is_file, chld->path);
+ for(int i = 0 ; i < chld->ninst ; ++i) {
+ dbgprintf("\tinst: %p\n", chld->instarr[i]);
+ }
+ for(act = chld->active ; act != NULL ; act = act->next) {
+ dbgprintf("\tact : %p\n", act);
+ dbgprintf("\tact : %p: name '%s', wd: %d\n",
+ act, act->name, act->wd);
+ }
+ }
+ for(chld = node->edges ; chld != NULL ; chld = chld->next) {
+ fs_node_print(chld->node, level+1);
+ }
+}
+
+static sbool
+isIgnoreOlderFile(const instanceConf_t *const inst, const char *const name)
+{
+ if (inst->ignoreOlderThan)
+ {
+ struct stat stat_buf;
+ time_t tt;
+ /* skip old files */
+ datetime.GetTime(&tt);
+ if (stat((char *)name, &stat_buf) == 0 && difftime(tt, stat_buf.st_mtime) > inst->ignoreOlderThan) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* add a new file system object if it not yet exists, ignore call
+ * if it already does.
+ */
+static rsRetVal ATTR_NONNULL(1,2)
+act_obj_add(fs_edge_t *const edge, const char *const name, const int is_file,
+ const ino_t ino, const int is_symlink, const char *const source)
+{
+ act_obj_t *act = NULL;
+ char basename[MAXFNAME];
+ DEFiRet;
+ int fd = -1;
+
+ DBGPRINTF("act_obj_add: edge %p, name '%s' (source '%s')\n", edge, name, source? source : "---");
+
+ if (isIgnoreOlderFile(edge->instarr[0], name)) {
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ for(act = edge->active ; act != NULL ; act = act->next) {
+ if(!strcmp(act->name, name)) {
+ if (!source || !act->source_name || !strcmp(act->source_name, source)) {
+ DBGPRINTF("active object '%s' already exists in '%s' - no need to add\n",
+ name, edge->path);
+ FINALIZE;
+ }
+ }
+ }
+ DBGPRINTF("need to add new active object '%s' in '%s' - checking if accessible\n", name, edge->path);
+ fd = open(name, O_RDONLY | O_CLOEXEC);
+ if(fd < 0) {
+ if (is_file) {
+ LogError(errno, RS_RET_ERR, "imfile: error accessing file '%s'", name);
+ } else { /* reporting only in debug for dirs as higher lvl paths are likely blocked by selinux */
+ DBGPRINTF("imfile: error accessing directory '%s'", name);
+ }
+ ABORT_FINALIZE(RS_RET_NO_FILE_ACCESS);
+ }
+ DBGPRINTF("add new active object '%s' in '%s'\n", name, edge->path);
+ CHKmalloc(act = calloc(sizeof(act_obj_t), 1));
+ CHKmalloc(act->name = strdup(name));
+ if (-1 == getBasename((uchar*)basename, (uchar*)name)) {
+ CHKmalloc(act->basename = strdup(name)); /* assume basename is same as name */
+ } else {
+ CHKmalloc(act->basename = strdup(basename));
+ }
+ act->edge = edge;
+ act->ino = ino;
+ act->fd = fd;
+ act->file_id[0] = '\0';
+ act->file_id_prev[0] = '\0';
+ act->is_symlink = is_symlink;
+ act->ratelimiter = NULL;
+ act->time_to_delete = 0;
+ if (source) { /* we are target of symlink */
+ CHKmalloc(act->source_name = strdup(source));
+ } else {
+ act->source_name = NULL;
+ }
+ #ifdef HAVE_INOTIFY_INIT
+ act->wd = in_setupWatch(act, is_file);
+ #endif
+ fen_setupWatch(act);
+ if(is_file && !is_symlink) {
+ const instanceConf_t *const inst = edge->instarr[0];// TODO: same file, multiple instances?
+ CHKiRet(ratelimitNew(&act->ratelimiter, "imfile", name));
+ CHKmalloc(act->multiSub.ppMsgs = malloc(inst->nMultiSub * sizeof(smsg_t *)));
+ act->multiSub.maxElem = inst->nMultiSub;
+ act->multiSub.nElem = 0;
+ pollFile(act);
+ }
+
+ /* all well, add to active list */
+ if(edge->active != NULL) {
+ edge->active->prev = act;
+ }
+ act->next = edge->active;
+ edge->active = act;
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(act != NULL) {
+ if (act->ratelimiter != NULL)
+ ratelimitDestruct(act->ratelimiter);
+ free(act->name);
+ free(act);
+ }
+ if(fd != -1) {
+ close(fd);
+ }
+ }
+ RETiRet;
+}
+
+
+/* this walks an edges active list and detects and acts on any changes
+ * seen there. It does NOT detect newly appeared files, as they are not
+ * inside the active list!
+ */
+static void
+detect_updates(fs_edge_t *const edge)
+{
+ act_obj_t *act;
+ struct stat fileInfo;
+ int restart = 0;
+
+ for(act = edge->active ; act != NULL ; act = act->next) {
+ DBGPRINTF("detect_updates checking active obj '%s'\n", act->name);
+ // lstat() has the disadvantage, that we get "deleted" when the name has changed
+ // but inode is still the same (like with logrotate)
+ int r = lstat(act->name, &fileInfo);
+ if(r == -1) { /* object gone away? */
+ /* now let's see if the file itself already exist (e.g. rotated away) */
+ /* NOTE: this will NOT stall the file. The reason is that when a new file
+ * with the same name is detected, we will not run into this code.
+ TODO: check the full implications, there are for sure some!
+ e.g. file has been closed, so we will never have old inode (but
+ why was it closed then? --> check)
+ */
+ r = fstat(act->ino, &fileInfo);
+ if(r == -1) {
+ time_t ttNow;
+ time(&ttNow);
+ if (act->time_to_delete == 0) {
+ act->time_to_delete = ttNow;
+ }
+ /* First time we run into this code, we need to give imfile a little time to process
+ * the old file in case a process is still writing into it until the FILE_DELETE_DELAY
+ * is reached OR the inode has changed (see elseif below). In most cases, the
+ * delay will never be reached and the file will be closed when the inode has changed.
+ * Directories are deleted without delay.
+ */
+ sbool is_file = act->edge->is_file;
+ if (!is_file || act->time_to_delete + FILE_DELETE_DELAY < ttNow) {
+ DBGPRINTF("detect_updates obj gone away, unlinking: "
+ "'%s', ttDelete: %lds, ttNow:%ld isFile: %d\n",
+ act->name, ttNow - (act->time_to_delete + FILE_DELETE_DELAY), ttNow, is_file);
+ act_obj_unlink(act);
+ restart = 1;
+ } else {
+ DBGPRINTF("detect_updates obj gone away, keep '%s' open: %ld/%ld/%lds!\n",
+ act->name, act->time_to_delete, ttNow, ttNow - act->time_to_delete);
+ pollFile(act);
+ }
+ }
+ break;
+ } else if(fileInfo.st_ino != act->ino) {
+ DBGPRINTF("file '%s' inode changed from %llu to %llu, unlinking from "
+ "internal lists\n", act->name, (long long unsigned) act->ino,
+ (long long unsigned) fileInfo.st_ino);
+ act_obj_unlink(act);
+ restart = 1;
+ break;
+ }
+ }
+
+ if (restart) {
+ detect_updates(edge);
+ }
+}
+
+
+/* check if active files need to be processed. This is only needed in
+ * polling mode.
+ */
+static void ATTR_NONNULL()
+poll_active_files(fs_edge_t *const edge)
+{
+ if( runModConf->opMode != OPMODE_POLLING
+ || !edge->is_file
+ || glbl.GetGlobalInputTermState() != 0) {
+ return;
+ }
+
+ act_obj_t *act;
+ for(act = edge->active ; act != NULL ; act = act->next) {
+ fen_setupWatch(act);
+ DBGPRINTF("poll_active_files: polling '%s'\n", act->name);
+ pollFile(act);
+ }
+}
+
+static rsRetVal ATTR_NONNULL()
+process_symlink(fs_edge_t *const chld, const char *symlink)
+{
+ DEFiRet;
+ char *target;
+ CHKmalloc(target = realpath(symlink, NULL));
+ struct stat fileInfo;
+ if(lstat(target, &fileInfo) != 0) {
+ LogError(errno, RS_RET_ERR, "imfile: process_symlink: cannot stat file '%s' - ignored", target);
+ FINALIZE;
+ }
+ const int is_file = (S_ISREG(fileInfo.st_mode));
+ DBGPRINTF("process_symlink: found '%s', File: %d (config file: %d), symlink: %d\n",
+ target, is_file, chld->is_file, 0);
+ if (act_obj_add(chld, target, is_file, fileInfo.st_ino, 0, symlink) == RS_RET_OK) {
+ /* need to watch parent target as well for proper rotation support */
+ uint idx = ustrlen(chld->active->name) - ustrlen(chld->active->basename);
+ if (idx) { /* basename is different from name */
+ char parent[MAXFNAME];
+ idx--; /* move past trailing slash */
+ memcpy(parent, chld->active->name, idx);
+ parent[idx] = '\0';
+ if(lstat(parent, &fileInfo) != 0) {
+ LogError(errno, RS_RET_ERR,
+ "imfile: process_symlink: cannot stat directory '%s' - ignored", parent);
+ FINALIZE;
+ }
+ if (chld->parent->root->edges) {
+ DBGPRINTF("process_symlink: adding parent '%s' of target '%s'\n", parent, target);
+ act_obj_add(chld->parent->root->edges, parent, 0, fileInfo.st_ino, 0, NULL);
+ }
+ }
+ }
+
+finalize_it:
+ free(target);
+ RETiRet;
+}
+
+static void ATTR_NONNULL()
+poll_tree(fs_edge_t *const chld)
+{
+ struct stat fileInfo;
+ glob_t files;
+ int need_globfree = 0;
+ int issymlink;
+ DBGPRINTF("poll_tree: chld %p, name '%s', path: %s\n", chld, chld->name, chld->path);
+ detect_updates(chld);
+ const int ret = glob((char*)chld->path, runModConf->sortFiles|GLOB_BRACE, NULL, &files);
+ need_globfree = 1;
+ DBGPRINTF("poll_tree: glob returned %d\n", ret);
+ if(ret == 0) {
+ DBGPRINTF("poll_tree: processing %d files\n", (int) files.gl_pathc);
+ for(unsigned i = 0 ; i < files.gl_pathc ; i++) {
+ if(glbl.GetGlobalInputTermState() != 0) {
+ goto done;
+ }
+ char *const file = files.gl_pathv[i];
+ if(lstat(file, &fileInfo) != 0) {
+ LogError(errno, RS_RET_ERR,
+ "imfile: poll_tree cannot stat file '%s' - ignored", file);
+ continue;
+ }
+
+ if (S_ISLNK(fileInfo.st_mode)) {
+ rsRetVal slink_ret = process_symlink(chld, file);
+ if (slink_ret != RS_RET_OK) {
+ continue;
+ }
+ issymlink = 1;
+ } else {
+ issymlink = 0;
+ }
+ const int is_file = (S_ISREG(fileInfo.st_mode) || issymlink);
+ DBGPRINTF("poll_tree: found '%s', File: %d (config file: %d), symlink: %d\n",
+ file, is_file, chld->is_file, issymlink);
+ if(!is_file && S_ISREG(fileInfo.st_mode)) {
+ LogMsg(0, RS_RET_ERR, LOG_WARNING,
+ "imfile: '%s' is neither a regular file, symlink, nor a "
+ "directory - ignored", file);
+ continue;
+ }
+ if(!issymlink && (chld->is_file != is_file)) {
+ LogMsg(0, RS_RET_ERR, LOG_WARNING,
+ "imfile: '%s' is %s but %s expected - ignored",
+ file, (is_file) ? "FILE" : "DIRECTORY",
+ (chld->is_file) ? "FILE" : "DIRECTORY");
+ continue;
+ }
+ act_obj_add(chld, file, is_file, fileInfo.st_ino, issymlink, NULL);
+ }
+ }
+
+ poll_active_files(chld);
+
+done:
+ if(need_globfree) {
+ globfree(&files);
+ }
+ return;
+}
+
+#ifdef HAVE_INOTIFY_INIT // TODO: shouldn't we use that in polling as well?
+static void ATTR_NONNULL()
+poll_timeouts(fs_edge_t *const edge)
+{
+ if(edge->is_file) {
+ act_obj_t *act;
+ for(act = edge->active ; act != NULL ; act = act->next) {
+ if(act->pStrm && strmReadMultiLine_isTimedOut(act->pStrm)) {
+ DBGPRINTF("timeout occurred on %s\n", act->name);
+ pollFile(act);
+ }
+ }
+ }
+}
+#endif
+
+
+/* destruct a single act_obj object */
+static void
+act_obj_destroy(act_obj_t *const act, const int is_deleted)
+{
+ uchar *statefn;
+ uchar statefile[MAXFNAME];
+ uchar toDel[MAXFNAME];
+
+ if(act == NULL)
+ return;
+
+ DBGPRINTF("act_obj_destroy: act %p '%s' (source '%s'), wd %d, pStrm %p, is_deleted %d, in_move %d\n",
+ act, act->name, act->source_name? act->source_name : "---", act->wd, act->pStrm, is_deleted,
+ act->in_move);
+ if(act->is_symlink && is_deleted) {
+ act_obj_t *target_act;
+ for(target_act = act->edge->active ; target_act != NULL ; target_act = target_act->next) {
+ if(target_act->source_name && !strcmp(target_act->source_name, act->name)) {
+ DBGPRINTF("act_obj_destroy: detect_updates for parent of target %s of %s symlink\n",
+ target_act->name, act->name);
+ detect_updates(target_act->edge->parent->root->edges);
+ break;
+ }
+ }
+ }
+ if(act->pStrm != NULL) {
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+ pollFile(act); /* get any left-over data */
+ if(inst->bRMStateOnDel) {
+ statefn = getStateFileName(act, statefile, sizeof(statefile));
+ getFullStateFileName(statefn, act->file_id, toDel, sizeof(toDel)); // TODO: check!
+ statefn = toDel;
+ }
+ persistStrmState(act);
+ strm.Destruct(&act->pStrm);
+ /* we delete state file after destruct in case strm obj initiated a write */
+ if(is_deleted && !act->in_move && inst->bRMStateOnDel) {
+ DBGPRINTF("act_obj_destroy: deleting state file %s\n", statefn);
+ unlink((char*)statefn);
+ }
+ }
+ if(act->ratelimiter != NULL) {
+ ratelimitDestruct(act->ratelimiter);
+ }
+ #ifdef HAVE_INOTIFY_INIT
+ if(act->wd != -1) {
+ inotify_rm_watch(ino_fd, act->wd);
+ wdmapDel(act->wd);
+ }
+ #endif
+ if(act->fd >= 0) {
+ close(act->fd);
+ }
+ #if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+ if(act->pfinf != NULL) {
+ free(act->pfinf->fobj.fo_name);
+ free(act->pfinf);
+ }
+ #endif
+ free(act->basename);
+ free(act->source_name);
+ free(act->multiSub.ppMsgs);
+ #if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
+ act->is_deleted = 1;
+ #else
+ free(act->name);
+ free(act);
+ #endif
+}
+
+
+/* destroy complete act list starting at given node */
+static void
+act_obj_destroy_all(act_obj_t *act)
+{
+ if(act == NULL)
+ return;
+
+ DBGPRINTF("act_obj_destroy_all: act %p '%s', wd %d, pStrm %p\n", act, act->name, act->wd, act->pStrm);
+ while(act != NULL) {
+ act_obj_t *const toDel = act;
+ act = act->next;
+ act_obj_destroy(toDel, 0);
+ }
+}
+
+#if 0
+/* debug: find if ptr is still present in list */
+static void
+chk_active(const act_obj_t *act, const act_obj_t *const deleted)
+{
+ while(act != NULL) {
+ DBGPRINTF("chk_active %p vs %p\n", act, deleted);
+ if(act->prev == deleted)
+ DBGPRINTF("chk_active %p prev points to %p\n", act, deleted);
+ if(act->next == deleted)
+ DBGPRINTF("chk_active %p next points to %p\n", act, deleted);
+ act = act->next;
+ DBGPRINTF("chk_active next %p\n", act);
+ }
+}
+#endif
+
+/* unlink act object from linked list and then
+ * destruct it.
+ */
+static void ATTR_NONNULL()
+act_obj_unlink(act_obj_t *act)
+{
+ DBGPRINTF("act_obj_unlink %p: %s, pStrm %p, ttDelete: %ld\n",
+ act, act->name, act->pStrm, act->time_to_delete);
+ if(act->prev == NULL) {
+ act->edge->active = act->next;
+ } else {
+ act->prev->next = act->next;
+ }
+ if(act->next != NULL) {
+ act->next->prev = act->prev;
+ }
+ act_obj_destroy(act, 1);
+ act = NULL;
+}
+
+static void
+fs_node_destroy(fs_node_t *const node)
+{
+ fs_edge_t *edge;
+ DBGPRINTF("node destroy: %p edges:\n", node);
+
+ for(edge = node->edges ; edge != NULL ; ) {
+ detect_updates(edge);
+ fs_node_destroy(edge->node);
+ fs_edge_t *const toDel = edge;
+ edge = edge->next;
+ act_obj_destroy_all(toDel->active);
+ free(toDel->name);
+ free(toDel->path);
+ free(toDel->instarr);
+ free(toDel);
+ }
+ free(node);
+}
+
+static void ATTR_NONNULL(1, 2)
+fs_node_walk(fs_node_t *const node,
+ void (*f_usr)(fs_edge_t*const))
+{
+ DBGPRINTF("node walk: %p edges:\n", node);
+
+ fs_edge_t *edge;
+ for(edge = node->edges ; edge != NULL ; edge = edge->next) {
+ DBGPRINTF("node walk: child %p '%s'\n", edge->node, edge->name);
+ f_usr(edge);
+ fs_node_walk(edge->node, f_usr);
+ }
+}
+
+
+
+/* add a file system object to config tree (or update existing node with new monitor)
+ */
+static rsRetVal
+fs_node_add(fs_node_t *const node,
+ fs_node_t *const source,
+ const uchar *const toFind,
+ const size_t pathIdx,
+ instanceConf_t *const inst)
+{
+ DEFiRet;
+ fs_edge_t *newchld = NULL;
+ int i;
+
+ DBGPRINTF("fs_node_add(%p, '%s') enter, idx %zd\n",
+ node, toFind+pathIdx, pathIdx);
+ assert(toFind[0] != '\0');
+ for(i = pathIdx ; (toFind[i] != '\0') && (toFind[i] != '/') ; ++i)
+ /*JUST SKIP*/;
+ const int isFile = (toFind[i] == '\0') ? 1 : 0;
+ uchar ourPath[PATH_MAX];
+ if(i == 0) {
+ ourPath[0] = '/';
+ ourPath[1] = '\0';
+ } else {
+ memcpy(ourPath, toFind, i);
+ ourPath[i] = '\0';
+ }
+ const size_t nextPathIdx = i+1;
+ const size_t len = i - pathIdx;
+ uchar name[PATH_MAX];
+ memcpy(name, toFind+pathIdx, len);
+ name[len] = '\0';
+ DBGPRINTF("fs_node_add: name '%s'\n", name);
+ node->root = source;
+
+ fs_edge_t *chld;
+ for(chld = node->edges ; chld != NULL ; chld = chld->next) {
+ if(!ustrcmp(chld->name, name)) {
+ DBGPRINTF("fs_node_add(%p, '%s') found '%s'\n", chld->node, toFind, name);
+ /* add new instance */
+ instanceConf_t **instarr_new = realloc(chld->instarr,
+ sizeof(instanceConf_t*) * (chld->ninst+1));
+ CHKmalloc(instarr_new);
+ chld->instarr = instarr_new;
+ chld->ninst++;
+ chld->instarr[chld->ninst-1] = inst;
+ /* recurse */
+ if(!isFile) {
+ CHKiRet(fs_node_add(chld->node, node, toFind, nextPathIdx, inst));
+ }
+ FINALIZE;
+ }
+ }
+
+ /* could not find node --> add it */
+ DBGPRINTF("fs_node_add(%p, '%s') did not find '%s' - adding it\n",
+ node, toFind, name);
+ CHKmalloc(newchld = calloc(sizeof(fs_edge_t), 1));
+ CHKmalloc(newchld->name = ustrdup(name));
+ CHKmalloc(newchld->node = calloc(sizeof(fs_node_t), 1));
+ CHKmalloc(newchld->path = ustrdup(ourPath));
+ CHKmalloc(newchld->instarr = calloc(sizeof(instanceConf_t*), 1));
+ newchld->instarr[0] = inst;
+ newchld->is_file = isFile;
+ newchld->ninst = 1;
+ newchld->parent = node;
+
+ DBGPRINTF("fs_node_add(%p, '%s') returns %p\n", node, toFind, newchld->node);
+
+ if(!isFile) {
+ CHKiRet(fs_node_add(newchld->node, node, toFind, nextPathIdx, inst));
+ }
+
+ /* link to list */
+ newchld->next = node->edges;
+ node->edges = newchld;
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(newchld != NULL) {
+ free(newchld->name);
+ free(newchld->node);
+ free(newchld->path);
+ free(newchld->instarr);
+ free(newchld);
+ }
+ }
+ RETiRet;
+}
+
+
+/* Helper function to combine statefile and state file directory
+ * This function is guranteed to work only on config data and DOES NOT
+ * open or otherwise modify disk file state.
+ */
+static int ATTR_NONNULL()
+getFullStateFileName(const uchar *const pszstatefile,
+ const char *const file_id,
+ uchar *const pszout,
+ const size_t ilenout)
+{
+ int lenout;
+ const uchar* pszstatedir;
+
+ /* Get Raw Workdir, if it is NULL we need to propper handle it */
+ pszstatedir = getStateFileDir();
+
+ /* Construct file name */
+ lenout = snprintf((char*)pszout, ilenout, "%s/%s%s%s",
+ (char*) (pszstatedir == NULL ? "." : (char*) pszstatedir), (char*)pszstatefile,
+ (*file_id == '\0') ? "" : ":", file_id);
+
+ /* return out length */
+ return lenout;
+}
+
+
+/* hash function for file-id
+ * Takes a block of data and returns a string with the hash value.
+ *
+ * Currently one provided by Aaaron Wiebe based on perl's hashing algorithm
+ * (so probably pretty generic). Not for excessively large strings!
+ * TODO: re-think the hash function!
+ */
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
+#endif
+static void __attribute__((nonnull(1,3)))
+#if defined(__clang__)
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+#endif
+get_file_id_hash(const char *data, size_t lendata,
+ char *const hash_str, const size_t len_hash_str)
+{
+ assert(len_hash_str >= 17); /* we always generate 8-byte strings */
+
+ size_t i;
+ uint8_t out[8], k[16];
+ for (i = 0; i < 16; ++i)
+ k[i] = i;
+ memset(out, 0, sizeof(out));
+ rs_siphash((const uint8_t *)data, lendata, k, out, 8);
+
+ for(i = 0 ; i < 8 ; ++i) {
+ if(2 * i+1 >= len_hash_str)
+ break;
+ snprintf(hash_str+(2*i), 3, "%2.2x", out[i]);
+ }
+}
+
+
+/* this returns the file-id for a given file
+ */
+static void ATTR_NONNULL(1)
+getFileID(act_obj_t *const act)
+{
+ char tmp_id[FILE_ID_HASH_SIZE];
+ strncpy(tmp_id, (const char*)act->file_id, FILE_ID_HASH_SIZE);
+ act->file_id[0] = '\0';
+ assert(act->fd >= 0); /* fd must have been opened at act_obj_t creation! */
+ char filedata[FILE_ID_SIZE];
+ lseek(act->fd, 0, SEEK_SET); /* Seek to beginning of file so we have correct id */
+ const int r = read(act->fd, filedata, FILE_ID_SIZE);
+ if(r == FILE_ID_SIZE) {
+ get_file_id_hash(filedata, sizeof(filedata), act->file_id, sizeof(act->file_id));
+ } else {
+ DBGPRINTF("getFileID partial or error read, ret %d\n", r);
+ }
+ if (strncmp(tmp_id, act->file_id, FILE_ID_HASH_SIZE)) {/* save the old id for cleaning purposes */
+ strncpy(act->file_id_prev, tmp_id, FILE_ID_HASH_SIZE);
+ }
+ DBGPRINTF("getFileID for '%s', file_id_hash '%s'\n", act->name, act->file_id);
+}
+
+/* this generates a state file name suitable for the given file. To avoid
+ * malloc calls, it must be passed a buffer which should be MAXFNAME large.
+ * Note: the buffer is not necessarily populated ... always ONLY use the
+ * RETURN VALUE!
+ * This function is guranteed to work only on config data and DOES NOT
+ * open or otherwise modify disk file state.
+ */
+static uchar * ATTR_NONNULL(1, 2)
+getStateFileName(const act_obj_t *const act,
+ uchar *const __restrict__ buf,
+ const size_t lenbuf)
+{
+ DBGPRINTF("getStateFileName for '%s'\n", act->name);
+ snprintf((char*)buf, lenbuf - 1, "imfile-state:%lld", (long long) act->ino);
+ DBGPRINTF("getStateFileName: state file name now is %s\n", buf);
+ return buf;
+}
+
+static rsRetVal
+checkPerMinuteRateLimits(per_minute_rate_limit_t *per_minute_rate_limits,
+ const size_t msgLen)
+{
+ DEFiRet;
+ time_t current_minute = time(NULL)/60;
+ if(per_minute_rate_limits->maxBytesPerMinute) {
+ if (per_minute_rate_limits->rateLimitingMinute == current_minute) {
+ per_minute_rate_limits->bytesThisMinute += msgLen;
+ /* if we would breach our rate limit then do not send the message. */
+ if (per_minute_rate_limits->bytesThisMinute > per_minute_rate_limits->maxBytesPerMinute) {
+ ABORT_FINALIZE(RS_RET_RATE_LIMITED);
+ }
+ } else {
+ per_minute_rate_limits->rateLimitingMinute = current_minute;
+ per_minute_rate_limits->bytesThisMinute = msgLen; /* Update count as message will be sent */
+ }
+ }
+ if(per_minute_rate_limits->maxLinesPerMinute) {
+ if (per_minute_rate_limits->rateLimitingMinute == current_minute) {
+ per_minute_rate_limits->linesThisMinute++;
+ /* if we would breach our rate limit then do not send the message. */
+ if (per_minute_rate_limits->linesThisMinute > per_minute_rate_limits->maxLinesPerMinute) {
+ ABORT_FINALIZE(RS_RET_RATE_LIMITED);
+ }
+ } else {
+ per_minute_rate_limits->rateLimitingMinute = current_minute;
+ per_minute_rate_limits->linesThisMinute = 1; /* Update count as message will be sent */
+ }
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* enqueue the read file line as a message. The provided string is
+ * not freed - this must be done by the caller.
+ */
+#define MAX_OFFSET_REPRESENTATION_NUM_BYTES 20
+static rsRetVal ATTR_NONNULL(1,2)
+enqLine(act_obj_t *const act,
+ cstr_t *const __restrict__ cstrLine,
+ const int64 strtOffs)
+{
+ DEFiRet;
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+ smsg_t *pMsg;
+ uchar file_offset[MAX_OFFSET_REPRESENTATION_NUM_BYTES+1];
+ const uchar *metadata_names[2] = {(uchar *)"filename",(uchar *)"fileoffset"} ;
+ const uchar *metadata_values[2] ;
+ const size_t msgLen = cstrLen(cstrLine);
+
+ if(msgLen == 0) {
+ /* we do not process empty lines */
+ FINALIZE;
+ }
+
+ CHKiRet(msgConstruct(&pMsg));
+ MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY);
+ MsgSetInputName(pMsg, pInputName);
+ if(inst->addCeeTag) {
+ /* Make sure we account for terminating null byte */
+ size_t ceeMsgSize = msgLen + CONST_LEN_CEE_COOKIE + 1;
+ char *ceeMsg;
+ CHKmalloc(ceeMsg = malloc(ceeMsgSize));
+ strcpy(ceeMsg, CONST_CEE_COOKIE);
+ strcat(ceeMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine));
+ MsgSetRawMsg(pMsg, ceeMsg, ceeMsgSize);
+ free(ceeMsg);
+ } else {
+ MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine), msgLen);
+ }
+ MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */
+ MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName()));
+ MsgSetTAG(pMsg, inst->pszTag, inst->lenTag);
+ msgSetPRI(pMsg, inst->iFacility | inst->iSeverity);
+ MsgSetRuleset(pMsg, inst->pBindRuleset);
+ if(inst->addMetadata) {
+ if (act->source_name) {
+ metadata_values[0] = (const uchar*)act->source_name;
+ } else {
+ metadata_values[0] = (const uchar*)act->name;
+ }
+ snprintf((char *)file_offset, MAX_OFFSET_REPRESENTATION_NUM_BYTES+1, "%lld", strtOffs);
+ metadata_values[1] = file_offset;
+ msgAddMultiMetadata(pMsg, metadata_names, metadata_values, 2);
+ }
+
+ if(inst->perMinuteRateLimits.maxBytesPerMinute || inst->perMinuteRateLimits.maxLinesPerMinute) {
+ CHKiRet(checkPerMinuteRateLimits((per_minute_rate_limit_t *)&inst->perMinuteRateLimits, msgLen));
+ }
+
+ if(inst->delay_perMsg) {
+ srSleep(inst->delay_perMsg % 1000000, inst->delay_perMsg / 1000000);
+ }
+
+ pMsg->msgFlags = pMsg->msgFlags | inst->msgFlag;
+
+ ratelimitAddMsg(act->ratelimiter, &act->multiSub, pMsg);
+finalize_it:
+ RETiRet;
+}
+/* try to open a file which has a state file. If the state file does not
+ * exist or cannot be read, an error is returned.
+ */
+static rsRetVal ATTR_NONNULL(1)
+openFileWithStateFile(act_obj_t *const act)
+{
+ DEFiRet;
+ uchar pszSFNam[MAXFNAME];
+ uchar statefile[MAXFNAME];
+ int fd = -1;
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+
+ uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
+ getFileID(act);
+
+ getFullStateFileName(statefn, act->file_id, pszSFNam, sizeof(pszSFNam));
+ DBGPRINTF("trying to open state for '%s', state file '%s'\n", act->name, pszSFNam);
+
+ /* check if the file exists */
+ fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
+ if(fd < 0) {
+ if(errno == ENOENT) {
+ if(act->file_id[0] != '\0') {
+ DBGPRINTF("state file %s for %s does not exist - trying to see if "
+ "inode-only file exists\n", pszSFNam, act->name);
+ getFullStateFileName(statefn, "", pszSFNam, sizeof(pszSFNam));
+ fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
+ if(fd >= 0) {
+ dbgprintf("found inode-only state file, will be renamed at next persist\n");
+ }
+ }
+ if(fd < 0) {
+ DBGPRINTF("state file %s for %s does not exist - trying to see if "
+ "old-style file exists\n", pszSFNam, act->name);
+ CHKiRet(OLD_openFileWithStateFile(act));
+ FINALIZE;
+ }
+ } else {
+ LogError(errno, RS_RET_IO_ERROR,
+ "imfile error trying to access state file for '%s'",
+ act->name);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ }
+
+ DBGPRINTF("opened state file %s for %s\n", pszSFNam, act->name);
+ CHKiRet(strm.Construct(&act->pStrm));
+
+ struct json_object *jval;
+ struct json_object *json = fjson_object_from_fd(fd);
+ if(json == NULL) {
+ LogError(0, RS_RET_ERR, "imfile: error reading state file for '%s'", act->name);
+ }
+
+ /* we access some data items a bit dirty, as we need to refactor the whole
+ * thing in any case - TODO
+ */
+ /* Note: we ignore filname property - it is just an aid to the user. Most
+ * importantly it *is wrong* after a file move!
+ */
+ fjson_object_object_get_ex(json, "prev_was_nl", &jval);
+ act->pStrm->bPrevWasNL = fjson_object_get_int(jval);
+
+ fjson_object_object_get_ex(json, "curr_offs", &jval);
+ act->pStrm->iCurrOffs = fjson_object_get_int64(jval);
+
+ fjson_object_object_get_ex(json, "strt_offs", &jval);
+ act->pStrm->strtOffs = fjson_object_get_int64(jval);
+
+ fjson_object_object_get_ex(json, "prev_line_segment", &jval);
+ const uchar *const prev_line_segment = (const uchar*)fjson_object_get_string(jval);
+ if(jval != NULL) {
+ CHKiRet(rsCStrConstructFromszStr(&act->pStrm->prevLineSegment, prev_line_segment));
+ cstrFinalize(act->pStrm->prevLineSegment);
+ uchar *ret = rsCStrGetSzStrNoNULL(act->pStrm->prevLineSegment);
+ DBGPRINTF("prev_line_segment present in state file 2, is: %s\n", ret);
+ }
+
+ fjson_object_object_get_ex(json, "prev_msg_segment", &jval);
+ const uchar *const prev_msg_segment = (const uchar*)fjson_object_get_string(jval);
+ if(jval != NULL) {
+ CHKiRet(rsCStrConstructFromszStr(&act->pStrm->prevMsgSegment, prev_msg_segment));
+ cstrFinalize(act->pStrm->prevMsgSegment);
+ uchar *ret = rsCStrGetSzStrNoNULL(act->pStrm->prevMsgSegment);
+ DBGPRINTF("prev_msg_segment present in state file 2, is: %s\n", ret);
+ }
+ fjson_object_put(json);
+
+ CHKiRet(strm.SetFName(act->pStrm, (uchar*)act->name, strlen(act->name)));
+ CHKiRet(strm.SettOperationsMode(act->pStrm, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(act->pStrm, STREAMTYPE_FILE_MONITOR));
+ CHKiRet(strm.SetFileNotFoundError(act->pStrm, inst->fileNotFoundError));
+ CHKiRet(strm.ConstructFinalize(act->pStrm));
+
+ CHKiRet(strm.SeekCurrOffs(act->pStrm));
+
+finalize_it:
+ if(fd >= 0) {
+ close(fd);
+ }
+ RETiRet;
+}
+
+/* try to open a file for which no state file exists. This function does NOT
+ * check if a state file actually exists or not -- this must have been
+ * checked before calling it.
+ */
+static rsRetVal
+openFileWithoutStateFile(act_obj_t *const act)
+{
+ DEFiRet;
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+
+ DBGPRINTF("clean startup withOUT state file for '%s'\n", act->name);
+ if(act->pStrm != NULL)
+ strm.Destruct(&act->pStrm);
+ CHKiRet(strm.Construct(&act->pStrm));
+ CHKiRet(strm.SettOperationsMode(act->pStrm, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(act->pStrm, STREAMTYPE_FILE_MONITOR));
+ CHKiRet(strm.SetFName(act->pStrm, (uchar*)act->name, strlen(act->name)));
+ CHKiRet(strm.SetFileNotFoundError(act->pStrm, inst->fileNotFoundError));
+ CHKiRet(strm.ConstructFinalize(act->pStrm));
+
+ /* As a state file not exist, this is a fresh start. seek to file end
+ * when freshStartTail is on.
+ */
+ if(inst->freshStartTail) {
+ const int fd = open(act->name, O_RDONLY | O_CLOEXEC);
+ if(fd >= 0) {
+ act->pStrm->iCurrOffs = lseek64(fd, 0, SEEK_END);
+ close(fd);
+ if(act->pStrm->iCurrOffs < 0) {
+ act->pStrm->iCurrOffs = 0;
+ LogError(errno, RS_RET_ERR, "imfile: could not query current "
+ "file size for %s - 'freshStartTail' option will "
+ "be ignored, starting at begin of file", inst->pszFileName);
+ }
+ CHKiRet(strm.SeekCurrOffs(act->pStrm));
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* try to open a file. This involves checking if there is a status file and,
+ * if so, reading it in. Processing continues from the last known location.
+ */
+static rsRetVal
+openFile(act_obj_t *const act)
+{
+ DEFiRet;
+ const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+
+ CHKiRet_Hdlr(openFileWithStateFile(act)) {
+ CHKiRet(openFileWithoutStateFile(act));
+ }
+
+ DBGPRINTF("breopenOnTruncate %d for '%s'\n", inst->reopenOnTruncate, act->name);
+ CHKiRet(strm.SetbReopenOnTruncate(act->pStrm, inst->reopenOnTruncate));
+ strmSetReadTimeout(act->pStrm, inst->readTimeout);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* The following is a cancel cleanup handler for strmReadLine(). It is necessary in case
+ * strmReadLine() is cancelled while processing the stream. -- rgerhards, 2008-03-27
+ */
+static void pollFileCancelCleanup(void *pArg)
+{
+ cstr_t **ppCStr = (cstr_t**) pArg;
+ if(*ppCStr != NULL)
+ rsCStrDestruct(ppCStr);
+}
+
+
+/* pollFile needs to be split due to the unfortunate pthread_cancel_push() macros. */
+static rsRetVal ATTR_NONNULL()
+pollFileReal(act_obj_t *act, cstr_t **pCStr)
+{
+ int64 strtOffs;
+ DEFiRet;
+ int64_t startOffs = 0;
+ int nProcessed = 0;
+ regex_t *start_preg = NULL, *end_preg = NULL;
+
+ DBGPRINTF("pollFileReal enter, act %p, pStrm %p, name '%s'\n", act, act->pStrm, act->name);
+ DBGPRINTF("pollFileReal enter, edge %p\n", act->edge);
+ DBGPRINTF("pollFileReal enter, edge->instarr %p\n", act->edge->instarr);
+
+ instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
+
+ if(act->pStrm == NULL) {
+ CHKiRet(openFile(act)); /* open file */
+ }
+
+ start_preg = (inst->startRegex == NULL) ? NULL : &inst->start_preg;
+ end_preg = (inst->endRegex == NULL) ? NULL : &inst->end_preg;
+
+ startOffs = act->pStrm->iCurrOffs;
+ /* loop below will be exited when strmReadLine() returns EOF */
+ while(glbl.GetGlobalInputTermState() == 0) {
+ if(inst->maxLinesAtOnce != 0 && nProcessed >= inst->maxLinesAtOnce)
+ break;
+ if((start_preg == NULL) && (end_preg == NULL)) {
+ CHKiRet(strm.ReadLine(act->pStrm, pCStr, inst->readMode, inst->escapeLF, inst->escapeLFString,
+ inst->trimLineOverBytes, &strtOffs));
+ } else {
+ CHKiRet(strmReadMultiLine(act->pStrm, pCStr, start_preg, end_preg,
+ inst->escapeLF, inst->escapeLFString, inst->discardTruncatedMsg,
+ inst->msgDiscardingError, &strtOffs));
+ }
+ ++nProcessed;
+ if(startOffs < FILE_ID_SIZE && act->pStrm->iCurrOffs >= FILE_ID_SIZE) {
+ dbgprintf("initiating state file write as sufficient data is now present; file=%s\n",
+ act->name);
+ persistStrmState(act);
+ startOffs = act->pStrm->iCurrOffs; /* disable check */
+ }
+ runModConf->bHadFileData = 1; /* this is just a flag, so set it and forget it */
+ CHKiRet(enqLine(act, *pCStr, strtOffs)); /* process line */
+ rsCStrDestruct(pCStr); /* discard string (must be done by us!) */
+ if(inst->iPersistStateInterval > 0 && ++act->nRecords >= inst->iPersistStateInterval) {
+ persistStrmState(act);
+ act->nRecords = 0;
+ }
+ }
+
+finalize_it:
+ multiSubmitFlush(&act->multiSub);
+ if(inst->bPersistStateAfterSubmission) {
+ persistStrmState(act);
+ }
+
+ if(*pCStr != NULL) {
+ rsCStrDestruct(pCStr);
+ }
+
+ RETiRet;
+}
+
+/* poll a file, need to check file rollover etc. open file if not open */
+static rsRetVal ATTR_NONNULL(1)
+pollFile(act_obj_t *const act)
+{
+ cstr_t *pCStr = NULL;
+ DEFiRet;
+ if (act->is_symlink) {
+ FINALIZE; /* no reason to poll symlink file */
+ }
+ /* Note: we must do pthread_cleanup_push() immediately, because the POSIX macros
+ * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14
+ */
+ pthread_cleanup_push(pollFileCancelCleanup, &pCStr);
+ iRet = pollFileReal(act, &pCStr);
+ pthread_cleanup_pop(0);
+finalize_it: RETiRet;
+}
+
+
+/* create input instance, set default parameters, and
+ * add it to the list of instances.
+ */
+static rsRetVal ATTR_NONNULL(1)
+createInstance(instanceConf_t **const pinst)
+{
+ instanceConf_t *inst;
+ DEFiRet;
+ CHKmalloc(inst = malloc(sizeof(instanceConf_t)));
+ inst->next = NULL;
+ inst->pBindRuleset = NULL;
+
+ inst->pszBindRuleset = NULL;
+ inst->pszFileName = NULL;
+ inst->pszTag = NULL;
+ inst->pszStateFile = NULL;
+ inst->nMultiSub = NUM_MULTISUB;
+ inst->iSeverity = 5;
+ inst->iFacility = 128;
+ inst->maxLinesAtOnce = 0;
+ inst->trimLineOverBytes = 0;
+ inst->iPersistStateInterval = 0;
+ inst->perMinuteRateLimits.maxBytesPerMinute = 0;
+ inst->perMinuteRateLimits.maxLinesPerMinute = 0;
+ inst->perMinuteRateLimits.rateLimitingMinute = 0;
+ inst->perMinuteRateLimits.linesThisMinute = 0;
+ inst->perMinuteRateLimits.bytesThisMinute = 0;
+ inst->bPersistStateAfterSubmission = 0;
+ inst->readMode = 0;
+ inst->startRegex = NULL;
+ inst->endRegex = NULL;
+ inst->discardTruncatedMsg = 0;
+ inst->msgDiscardingError = 1;
+ inst->bRMStateOnDel = 1;
+ inst->escapeLF = 1;
+ inst->escapeLFString = NULL;
+ inst->reopenOnTruncate = 0;
+ inst->addMetadata = ADD_METADATA_UNSPECIFIED;
+ inst->addCeeTag = 0;
+ inst->freshStartTail = 0;
+ inst->fileNotFoundError = 1;
+ inst->readTimeout = loadModConf->readTimeout;
+ inst->delay_perMsg = 0;
+ inst->msgFlag = 0;
+ inst->ignoreOlderThan = 0;
+
+ /* node created, let's add to config */
+ if(loadModConf->tail == NULL) {
+ loadModConf->tail = loadModConf->root = inst;
+ } else {
+ loadModConf->tail->next = inst;
+ loadModConf->tail = inst;
+ }
+
+ *pinst = inst;
+finalize_it:
+ RETiRet;
+}
+
+
+/* the basen(ame) buffer must be of size MAXFNAME
+ * returns the index of the slash in front of basename
+ */
+static int ATTR_NONNULL()
+getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path)
+{
+ int i;
+ int found = 0;
+ const int lenName = ustrlen(path);
+ for(i = lenName ; i >= 0 ; --i) {
+ if(path[i] == '/') {
+ /* found basename component */
+ found = 1;
+ if(i == lenName)
+ basen[0] = '\0';
+ else {
+ memcpy(basen, path+i+1, lenName-i);
+ }
+ break;
+ }
+ }
+ if (found == 1)
+ return i;
+ else {
+ return -1;
+ }
+}
+
+/* this function checks instance parameters and does some required pre-processing
+ */
+static rsRetVal ATTR_NONNULL()
+checkInstance(instanceConf_t *const inst)
+{
+ uchar curr_wd[MAXFNAME];
+ DEFiRet;
+
+ /* this is primarily for the clang static analyzer, but also
+ * guards against logic errors in the config handler.
+ */
+ if(inst->pszFileName == NULL)
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+
+ CHKmalloc(inst->pszFileName_forOldStateFile = ustrdup(inst->pszFileName));
+ if(loadModConf->normalizePath) {
+ if(inst->pszFileName[0] == '.' && inst->pszFileName[1] == '/') {
+ DBGPRINTF("imfile: removing heading './' from name '%s'\n", inst->pszFileName);
+ memmove(inst->pszFileName, inst->pszFileName+2, ustrlen(inst->pszFileName) - 1);
+ }
+
+ if(inst->pszFileName[0] != '/') {
+ if(getcwd((char*)curr_wd, MAXFNAME) == NULL || curr_wd[0] != '/') {
+ LogError(errno, RS_RET_ERR, "imfile: error querying current working "
+ "directory - can not continue with %s", inst->pszFileName);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ const size_t len_curr_wd = ustrlen(curr_wd);
+ if(len_curr_wd + ustrlen(inst->pszFileName) + 1 >= MAXFNAME) {
+ LogError(0, RS_RET_ERR, "imfile: length of configured file and current "
+ "working directory exceeds permitted size - ignoring %s",
+ inst->pszFileName);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ curr_wd[len_curr_wd] = '/';
+ strcpy((char*)curr_wd+len_curr_wd+1, (char*)inst->pszFileName);
+ free(inst->pszFileName);
+ CHKmalloc(inst->pszFileName = ustrdup(curr_wd));
+ }
+ }
+ dbgprintf("imfile: adding file monitor for '%s'\n", inst->pszFileName);
+
+ if(inst->pszTag != NULL) {
+ inst->lenTag = ustrlen(inst->pszTag);
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* add a new monitor */
+static rsRetVal
+addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal)
+{
+ instanceConf_t *inst;
+ DEFiRet;
+
+ if(cs.pszFileName == NULL) {
+ LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no file name given, file monitor can "
+ "not be created");
+ ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
+ }
+ if(cs.pszFileTag == NULL) {
+ LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no tag value given, file monitor can "
+ "not be created");
+ ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
+ }
+
+ CHKiRet(createInstance(&inst));
+ if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) {
+ inst->pszBindRuleset = NULL;
+ } else {
+ CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset));
+ }
+ CHKmalloc(inst->pszFileName = ustrdup((char*) cs.pszFileName));
+ CHKmalloc(inst->pszTag = ustrdup((char*) cs.pszFileTag));
+ if(cs.pszStateFile == NULL) {
+ inst->pszStateFile = NULL;
+ } else {
+ CHKmalloc(inst->pszStateFile = ustrdup(cs.pszStateFile));
+ }
+ inst->iSeverity = cs.iSeverity;
+ inst->iFacility = cs.iFacility;
+ if(cs.maxLinesAtOnce) {
+ if(loadModConf->opMode == OPMODE_INOTIFY) {
+ LogError(0, RS_RET_PARAM_NOT_PERMITTED,
+ "parameter \"maxLinesAtOnce\" not "
+ "permited in inotify mode - ignored");
+ } else {
+ inst->maxLinesAtOnce = cs.maxLinesAtOnce;
+ }
+ }
+ inst->trimLineOverBytes = cs.trimLineOverBytes;
+ inst->ignoreOlderThan = 0;
+ inst->iPersistStateInterval = cs.iPersistStateInterval;
+ inst->perMinuteRateLimits.maxBytesPerMinute = cs.maxBytesPerMinute;
+ inst->perMinuteRateLimits.maxLinesPerMinute = cs.maxLinesPerMinute;
+ inst->bPersistStateAfterSubmission = 0;
+ inst->readMode = cs.readMode;
+ inst->escapeLF = 0;
+ inst->escapeLFString = NULL;
+ inst->reopenOnTruncate = 0;
+ inst->addMetadata = 0;
+ inst->addCeeTag = 0;
+ inst->bRMStateOnDel = 0;
+ inst->readTimeout = loadModConf->readTimeout;
+ inst->msgFlag = 0;
+
+ CHKiRet(checkInstance(inst));
+
+ /* reset legacy system */
+ cs.iPersistStateInterval = 0;
+ resetConfigVariables(NULL, NULL); /* values are both dummies */
+
+finalize_it:
+ free(pNewVal); /* we do not need it, but we must free it! */
+ RETiRet;
+}
+
+
+BEGINnewInpInst
+ struct cnfparamvals *pvals;
+ instanceConf_t *inst;
+ int i;
+CODESTARTnewInpInst
+ DBGPRINTF("newInpInst (imfile)\n");
+
+ pvals = nvlstGetParams(lst, &inppblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ if(Debug) {
+ DBGPRINTF("input param blk in imfile:\n");
+ cnfparamsPrint(&inppblk, pvals);
+ }
+
+ CHKiRet(createInstance(&inst));
+
+ for(i = 0 ; i < inppblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(inppblk.descr[i].name, "file")) {
+ inst->pszFileName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "statefile")) {
+ inst->pszStateFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "removestateondelete")) {
+ inst->bRMStateOnDel = (uint8_t) pvals[i].val.d.n; // TODO: duplicate!
+ } else if(!strcmp(inppblk.descr[i].name, "tag")) {
+ inst->pszTag = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "ruleset")) {
+ inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "severity")) {
+ inst->iSeverity = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "facility")) {
+ inst->iFacility = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "readmode")) {
+ inst->readMode = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "startmsg.regex")) {
+ inst->startRegex = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "endmsg.regex")) {
+ inst->endRegex = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "discardtruncatedmsg")) {
+ inst->discardTruncatedMsg = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "msgdiscardingerror")) {
+ inst->msgDiscardingError = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "deletestateonfiledelete")) {
+ inst->bRMStateOnDel = (sbool) pvals[i].val.d.n; // TODO: duplicate!
+ } else if(!strcmp(inppblk.descr[i].name, "addmetadata")) {
+ inst->addMetadata = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "delay.message")) {
+ inst->delay_perMsg = (unsigned) pvals[i].val.d.n;
+ } else if (!strcmp(inppblk.descr[i].name, "addceetag")) {
+ inst->addCeeTag = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "freshstarttail")) {
+ inst->freshStartTail = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "filenotfounderror")) {
+ inst->fileNotFoundError = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "escapelf")) {
+ inst->escapeLF = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "escapelf.replacement")) {
+ inst->escapeLFString = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "reopenontruncate")) {
+ inst->reopenOnTruncate = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxlinesatonce")) {
+ if( loadModConf->opMode == OPMODE_INOTIFY
+ && pvals[i].val.d.n > 0) {
+ LogError(0, RS_RET_PARAM_NOT_PERMITTED,
+ "parameter \"maxLinesAtOnce\" not "
+ "permited in inotify mode - ignored");
+ } else {
+ inst->maxLinesAtOnce = pvals[i].val.d.n;
+ }
+ } else if(!strcmp(inppblk.descr[i].name, "trimlineoverbytes")) {
+ inst->trimLineOverBytes = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "ignoreolderthan")) {
+ inst->ignoreOlderThan = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "persiststateinterval")) {
+ inst->iPersistStateInterval = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxbytesperminute")) {
+ DBGPRINTF("imfile: enabling maxbytesperminute ratelimiting\n");
+ inst->perMinuteRateLimits.maxBytesPerMinute = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxlinesperminute")) {
+ DBGPRINTF("imfile: enabling maxlinesperminute ratelimiting\n");
+ inst->perMinuteRateLimits.maxLinesPerMinute = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "persiststateaftersubmission")) {
+ inst->bPersistStateAfterSubmission = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxsubmitatonce")) {
+ inst->nMultiSub = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "readtimeout")) {
+ inst->readTimeout = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "needparse")) {
+ inst->msgFlag = pvals[i].val.d.n ? NEEDS_PARSING : 0;
+ } else {
+ DBGPRINTF("program error, non-handled "
+ "param '%s'\n", inppblk.descr[i].name);
+ }
+ }
+ i = (inst->readMode > 0) ? 1 : 0;
+ i = (NULL != inst->startRegex) ? (i+1) : i;
+ i = (NULL != inst->endRegex) ? (i+1) : i;
+ if(i > 1) {
+ LogError(0, RS_RET_PARAM_NOT_PERMITTED,
+ "only one of readMode or startmsg.regex or endmsg.regex can be set "
+ "at the same time");
+ ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
+ }
+
+ if(inst->startRegex != NULL) {
+ const int errcode = regcomp(&inst->start_preg, (char*)inst->startRegex, REG_EXTENDED);
+ if(errcode != 0) {
+ char errbuff[512];
+ regerror(errcode, &inst->start_preg, errbuff, sizeof(errbuff));
+ parser_errmsg("imfile: error in startmsg.regex expansion: %s", errbuff);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+ if(inst->endRegex != NULL) {
+ const int errcode = regcomp(&inst->end_preg, (char*)inst->endRegex, REG_EXTENDED);
+ if(errcode != 0) {
+ char errbuff[512];
+ regerror(errcode, &inst->end_preg, errbuff, sizeof(errbuff));
+ parser_errmsg("imfile: error in endmsg.regex expansion: %s", errbuff);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+ if(inst->readTimeout != 0)
+ loadModConf->haveReadTimeouts = 1;
+ iRet = checkInstance(inst);
+finalize_it:
+CODE_STD_FINALIZERnewInpInst
+ cnfparamvalsDestruct(pvals, &inppblk);
+ENDnewInpInst
+
+BEGINbeginCnfLoad
+CODESTARTbeginCnfLoad
+ loadModConf = pModConf;
+ currModConf = pModConf;
+ pModConf->pConf = pConf;
+ /* init our settings */
+ loadModConf->opMode = OPMODE_POLLING;
+ loadModConf->iPollInterval = DFLT_PollInterval;
+ loadModConf->configSetViaV2Method = 0;
+ loadModConf->readTimeout = 0; /* default: no timeout */
+ loadModConf->timeoutGranularity = 1000; /* default: 1 second */
+ loadModConf->haveReadTimeouts = 0; /* default: no timeout */
+ loadModConf->normalizePath = 1;
+ loadModConf->sortFiles = GLOB_NOSORT;
+ loadModConf->stateFileDirectory = NULL;
+ loadModConf->conf_tree = calloc(sizeof(fs_node_t), 1);
+ loadModConf->conf_tree->edges = NULL;
+ bLegacyCnfModGlobalsPermitted = 1;
+ /* init legacy config vars */
+ cs.pszFileName = NULL;
+ cs.pszFileTag = NULL;
+ cs.pszStateFile = NULL;
+ cs.iPollInterval = DFLT_PollInterval;
+ cs.iPersistStateInterval = 0;
+ cs.iFacility = 128;
+ cs.iSeverity = 5;
+ cs.readMode = 0;
+ cs.maxLinesAtOnce = 10240;
+ cs.trimLineOverBytes = 0;
+ENDbeginCnfLoad
+
+
+BEGINsetModCnf
+ struct cnfparamvals *pvals = NULL;
+ int i;
+CODESTARTsetModCnf
+ #if defined(HAVE_PORT_SOURCE_FILE)
+ /* this means we are on Solaris, so inotify is not there */
+ loadModConf->opMode = OPMODE_FEN;
+ #elif defined(HAVE_INOTIFY_INIT)
+ loadModConf->opMode = OPMODE_INOTIFY;
+ #else
+ loadModConf->opMode = OPMODE_POLLING;
+ #endif
+ pvals = nvlstGetParams(lst, &modpblk, NULL);
+ if(pvals == NULL) {
+ LogError(0, RS_RET_MISSING_CNFPARAMS, "imfile: error processing module "
+ "config parameters [module(...)]");
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ if(Debug) {
+ DBGPRINTF("module (global) param blk for imfile:\n");
+ cnfparamsPrint(&modpblk, pvals);
+ }
+
+ for(i = 0 ; i < modpblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(modpblk.descr[i].name, "pollinginterval")) {
+ loadModConf->iPollInterval = (int) pvals[i].val.d.n;
+ } else if(!strcmp(modpblk.descr[i].name, "readtimeout")) {
+ loadModConf->readTimeout = (int) pvals[i].val.d.n;
+ } else if(!strcmp(modpblk.descr[i].name, "timeoutgranularity")) {
+ /* note: we need ms, thus "* 1000" */
+ loadModConf->timeoutGranularity = (int) pvals[i].val.d.n * 1000;
+ } else if(!strcmp(modpblk.descr[i].name, "sortfiles")) {
+ loadModConf->sortFiles = ((sbool) pvals[i].val.d.n) ? 0 : GLOB_NOSORT;
+ } else if(!strcmp(modpblk.descr[i].name, "statefile.directory")) {
+ loadModConf->stateFileDirectory = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(modpblk.descr[i].name, "normalizepath")) {
+ loadModConf->normalizePath = (sbool) pvals[i].val.d.n;
+ } else if(!strcmp(modpblk.descr[i].name, "mode")) {
+ if(!es_strconstcmp(pvals[i].val.d.estr, "polling"))
+ loadModConf->opMode = OPMODE_POLLING;
+ else if(!es_strconstcmp(pvals[i].val.d.estr, "inotify")) {
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE) /* use FEN on Solaris! */
+ loadModConf->opMode = OPMODE_FEN;
+ DBGPRINTF("inotify mode configured, but only FEN "
+ "is available on OS SOLARIS. Switching to FEN "
+ "Mode automatically\n");
+#else
+ #if defined(HAVE_INOTIFY_INIT)
+ loadModConf->opMode = OPMODE_INOTIFY;
+ #else
+ loadModConf->opMode = OPMODE_POLLING;
+ #endif
+#endif
+ } else if(!es_strconstcmp(pvals[i].val.d.estr, "fen"))
+ loadModConf->opMode = OPMODE_FEN;
+ else {
+ char *cstr = es_str2cstr(pvals[i].val.d.estr, NULL);
+ LogError(0, RS_RET_PARAM_ERROR, "imfile: unknown "
+ "mode '%s'", cstr);
+ free(cstr);
+ }
+ } else {
+ DBGPRINTF("program error, non-handled "
+ "param '%s' in beginCnfLoad\n", modpblk.descr[i].name);
+ }
+ }
+
+ /* remove all of our legacy handlers, as they can not used in addition
+ * the the new-style config method.
+ */
+ bLegacyCnfModGlobalsPermitted = 0;
+ loadModConf->configSetViaV2Method = 1;
+
+finalize_it:
+ if(pvals != NULL)
+ cnfparamvalsDestruct(pvals, &modpblk);
+ENDsetModCnf
+
+
+BEGINendCnfLoad
+CODESTARTendCnfLoad
+ if(!loadModConf->configSetViaV2Method) {
+ /* persist module-specific settings from legacy config system */
+ loadModConf->iPollInterval = cs.iPollInterval;
+ }
+ DBGPRINTF("opmode is %d, polling interval is %d\n",
+ loadModConf->opMode,
+ loadModConf->iPollInterval);
+
+ loadModConf = NULL; /* done loading */
+ /* free legacy config vars */
+ free(cs.pszFileName);
+ free(cs.pszFileTag);
+ free(cs.pszStateFile);
+ENDendCnfLoad
+
+
+BEGINcheckCnf
+ instanceConf_t *inst;
+CODESTARTcheckCnf
+ if(getStateFileDir() == NULL) {
+ /* this intentionally is an error message */
+ LogError(0, RS_RET_NO_WRKDIR_SET,
+ "imfile: no working or state file directory set, imfile will create "
+ "state files in the current working directory (probably "
+ "the root dir). Use global(workDirectory=\"/some/path\") "
+ "to set the working directory");
+ }
+ for(inst = pModConf->root ; inst != NULL ; inst = inst->next) {
+ std_checkRuleset(pModConf, inst);
+ }
+ if(pModConf->root == NULL) {
+ LogError(0, RS_RET_NO_LISTNERS,
+ "imfile: no files configured to be monitored - "
+ "no input will be gathered");
+ iRet = RS_RET_NO_LISTNERS;
+ }
+ENDcheckCnf
+
+
+/* note: we do access files AFTER we have dropped privileges. This is
+ * intentional, user must make sure the files have the right permissions.
+ */
+BEGINactivateCnf
+ instanceConf_t *inst;
+CODESTARTactivateCnf
+ runModConf = pModConf;
+ currModConf = pModConf;
+ if(runModConf->root == NULL) {
+ LogError(0, NO_ERRCODE, "imfile: no file monitors configured, "
+ "input not activated.\n");
+ ABORT_FINALIZE(RS_RET_NO_RUN);
+ }
+
+ for(inst = runModConf->root ; inst != NULL ; inst = inst->next) {
+ // TODO: provide switch to turn off this warning?
+ if(!containsGlobWildcard((char*)inst->pszFileName)) {
+ if(access((char*)inst->pszFileName, R_OK) != 0) {
+ LogError(errno, RS_RET_ERR,
+ "imfile: on startup file '%s' does not exist "
+ "but is configured in static file monitor - this "
+ "may indicate a misconfiguration. If the file "
+ "appears at a later time, it will automatically "
+ "be processed. Reason", inst->pszFileName);
+ }
+ }
+ fs_node_add(runModConf->conf_tree, NULL, inst->pszFileName, 0, inst);
+ }
+
+ if(Debug) {
+ fs_node_print(runModConf->conf_tree, 0);
+ }
+
+finalize_it:
+ENDactivateCnf
+
+
+BEGINfreeCnf
+ instanceConf_t *inst, *del;
+CODESTARTfreeCnf
+ fs_node_destroy(pModConf->conf_tree);
+ for(inst = pModConf->root ; inst != NULL ; ) {
+ free(inst->pszBindRuleset);
+ free(inst->pszFileName);
+ free(inst->pszTag);
+ free(inst->pszStateFile);
+ free(inst->pszFileName_forOldStateFile);
+ if(inst->startRegex != NULL) {
+ regfree(&inst->start_preg);
+ free(inst->startRegex);
+ }
+ if(inst->endRegex != NULL) {
+ regfree(&inst->end_preg);
+ free(inst->endRegex);
+ }
+ del = inst;
+ inst = inst->next;
+ free(del);
+ }
+ENDfreeCnf
+
+
+/* initial poll run, to be used for all modes. Depending on mode, it does some
+ * further initializations (e.g. watches in inotify mode). Most importantly,
+ * it processes already-existing files, which would not otherwise be picked
+ * up in notifcation modes (inotfiy, FEN). Also, when freshStartTail is set,
+ * this run assumes that all previous existing data exists and needs not
+ * to be considered.
+ * Note: there is a race on files created *during* the run, but that race is
+ * inevitable (and thus freshStartTail is actually broken, but users still seem
+ * to want it...).
+ * rgerhards, 2018-05-17
+ */
+static void
+do_initial_poll_run(void)
+{
+ fs_node_walk(runModConf->conf_tree, poll_tree);
+
+ /* fresh start done, so disable freshStartTail for files that now will be created */
+ for(instanceConf_t *inst = runModConf->root ; inst != NULL ; inst = inst->next) {
+ inst->freshStartTail = 0;
+ }
+}
+
+
+/* Monitor files in polling mode. */
+static rsRetVal
+doPolling(void)
+{
+ DEFiRet;
+ do_initial_poll_run();
+ while(glbl.GetGlobalInputTermState() == 0) {
+ DBGPRINTF("doPolling: new poll run\n");
+ do {
+ runModConf->bHadFileData = 0;
+ fs_node_walk(runModConf->conf_tree, poll_tree);
+ DBGPRINTF("doPolling: end poll walk, hadData %d\n", runModConf->bHadFileData);
+ } while(runModConf->bHadFileData); /* warning: do...while()! */
+
+ /* Note: the additional 10ns wait is vitally important. It guards rsyslog
+ * against totally hogging the CPU if the users selects a polling interval
+ * of 0 seconds. It doesn't hurt any other valid scenario. So do not remove.
+ * rgerhards, 2008-02-14
+ */
+ DBGPRINTF("doPolling: poll going to sleep\n");
+ if(glbl.GetGlobalInputTermState() == 0)
+ srSleep(runModConf->iPollInterval, 10);
+ }
+
+ RETiRet;
+}
+
+#if defined(HAVE_INOTIFY_INIT)
+
+static void ATTR_NONNULL(1)
+in_dbg_showEv(const struct inotify_event *ev)
+{
+ if(!Debug)
+ return;
+ if(ev->mask & IN_IGNORED) {
+ dbgprintf("INOTIFY event: watch was REMOVED\n");
+ }
+ if(ev->mask & IN_MODIFY) {
+ dbgprintf("INOTIFY event: watch was MODIFID\n");
+ }
+ if(ev->mask & IN_ACCESS) {
+ dbgprintf("INOTIFY event: watch IN_ACCESS\n");
+ }
+ if(ev->mask & IN_ATTRIB) {
+ dbgprintf("INOTIFY event: watch IN_ATTRIB\n");
+ }
+ if(ev->mask & IN_CLOSE_WRITE) {
+ dbgprintf("INOTIFY event: watch IN_CLOSE_WRITE\n");
+ }
+ if(ev->mask & IN_CLOSE_NOWRITE) {
+ dbgprintf("INOTIFY event: watch IN_CLOSE_NOWRITE\n");
+ }
+ if(ev->mask & IN_CREATE) {
+ dbgprintf("INOTIFY event: file was CREATED: %s\n", ev->name);
+ }
+ if(ev->mask & IN_DELETE) {
+ dbgprintf("INOTIFY event: watch IN_DELETE\n");
+ }
+ if(ev->mask & IN_DELETE_SELF) {
+ dbgprintf("INOTIFY event: watch IN_DELETE_SELF\n");
+ }
+ if(ev->mask & IN_MOVE_SELF) {
+ dbgprintf("INOTIFY event: watch IN_MOVE_SELF\n");
+ }
+ if(ev->mask & IN_MOVED_FROM) {
+ dbgprintf("INOTIFY event: watch IN_MOVED_FROM, cookie %u, name '%s'\n", ev->cookie, ev->name);
+ }
+ if(ev->mask & IN_MOVED_TO) {
+ dbgprintf("INOTIFY event: watch IN_MOVED_TO, cookie %u, name '%s'\n", ev->cookie, ev->name);
+ }
+ if(ev->mask & IN_OPEN) {
+ dbgprintf("INOTIFY event: watch IN_OPEN\n");
+ }
+ if(ev->mask & IN_ISDIR) {
+ dbgprintf("INOTIFY event: watch IN_ISDIR\n");
+ }
+}
+
+
+static void ATTR_NONNULL(1, 2)
+in_handleFileEvent(struct inotify_event *ev, const wd_map_t *const etry)
+{
+ if(ev->mask & IN_MODIFY) {
+ DBGPRINTF("fs_node_notify_file_update: act->name '%s'\n", etry->act->name);
+ pollFile(etry->act);
+ } else {
+ DBGPRINTF("got non-expected inotify event:\n");
+ in_dbg_showEv(ev);
+ }
+}
+
+
+/* workaround for IN_MOVED: walk active list and prevent state file deletion of
+ * IN_MOVED_IN active object
+ * TODO: replace by a more generic solution.
+ */
+static void
+flag_in_move(fs_edge_t *const edge, const char *name_moved)
+{
+ act_obj_t *act;
+
+ for(act = edge->active ; act != NULL ; act = act->next) {
+ DBGPRINTF("checking active object %s\n", act->basename);
+ if(!strcmp(act->basename, name_moved)){
+ DBGPRINTF("found file\n");
+ act->in_move = 1;
+ break;
+ } else {
+ DBGPRINTF("name check fails, '%s' != '%s'\n", act->basename, name_moved);
+ }
+ }
+ if (!act && edge->next) {
+ flag_in_move(edge->next, name_moved);
+ }
+}
+
+static void ATTR_NONNULL(1)
+in_processEvent(struct inotify_event *ev)
+{
+ if(ev->mask & IN_IGNORED) {
+ DBGPRINTF("imfile: got IN_IGNORED event\n");
+ goto done;
+ }
+
+ DBGPRINTF("in_processEvent process Event %x for %s\n", ev->mask, ev->name);
+ const wd_map_t *const etry = wdmapLookup(ev->wd);
+ if(etry == NULL) {
+ LogMsg(0, RS_RET_INTERNAL_ERROR, LOG_WARNING, "imfile: internal error? "
+ "inotify provided watch descriptor %d which we could not find "
+ "in our tables - ignored", ev->wd);
+ goto done;
+ }
+ DBGPRINTF("in_processEvent process Event %x is_file %d, act->name '%s'\n",
+ ev->mask, etry->act->edge->is_file, etry->act->name);
+
+ if((ev->mask & IN_MOVED_FROM)) {
+ flag_in_move(etry->act->edge->node->edges, ev->name);
+ }
+ if(ev->mask & (IN_MOVED_FROM | IN_MOVED_TO)) {
+ fs_node_walk(etry->act->edge->node, poll_tree);
+ } else if(etry->act->edge->is_file && !(etry->act->is_symlink)) {
+ in_handleFileEvent(ev, etry); // esentially poll_file()!
+ } else {
+ fs_node_walk(etry->act->edge->node, poll_tree);
+ }
+done: return;
+}
+
+
+/* Monitor files in inotify mode */
+static rsRetVal
+do_inotify(void)
+{
+ char iobuf[8192];
+ int rd;
+ int currev;
+ static int last_timeout = 0;
+ struct pollfd pollfd;
+ DEFiRet;
+
+ CHKiRet(wdmapInit());
+ ino_fd = inotify_init1(IN_NONBLOCK);
+ if(ino_fd < 0) {
+ LogError(errno, RS_RET_INOTIFY_INIT_FAILED, "imfile: Init inotify "
+ "instance failed ");
+ return RS_RET_INOTIFY_INIT_FAILED;
+ }
+ DBGPRINTF("inotify fd %d\n", ino_fd);
+
+ do_initial_poll_run();
+
+ while(glbl.GetGlobalInputTermState() == 0) {
+ int r;
+
+ pollfd.fd = ino_fd;
+ pollfd.events = POLLIN;
+
+ if (runModConf->haveReadTimeouts)
+ r = poll(&pollfd, 1, runModConf->timeoutGranularity);
+ else
+ r = poll(&pollfd, 1, -1);
+
+ if (r == -1 && errno == EINTR) {
+ DBGPRINTF("do_inotify interrupted while polling on ino_fd\n");
+ continue;
+ }
+ if(r == 0) {
+ DBGPRINTF("readTimeouts are configured, checking if some apply\n");
+ if (runModConf->haveReadTimeouts) {
+ fs_node_walk(runModConf->conf_tree, poll_timeouts);
+ last_timeout = time(NULL);
+ }
+ continue;
+ } else if (r == -1) {
+ LogError(errno, RS_RET_INTERNAL_ERROR,
+ "%s:%d: unexpected error during poll timeout wait",
+ __FILE__, __LINE__);
+ /* we do not abort, as this would render the whole input defunct */
+ continue;
+ } else if(r != 1) {
+ LogError(errno, RS_RET_INTERNAL_ERROR,
+ "%s:%d: ERROR: poll returned more fds (%d) than given to it (1)",
+ __FILE__, __LINE__, r);
+ /* we do not abort, as this would render the whole input defunct */
+ continue;
+ }
+ else {
+ // process timeouts always, ino_fd may be too busy to ever have timeout occur from poll
+ if(runModConf->haveReadTimeouts) {
+ int now = time(NULL);
+ if(last_timeout + (runModConf->timeoutGranularity / 1000) > now) {
+ fs_node_walk(runModConf->conf_tree, poll_timeouts);
+ last_timeout = time(NULL);
+ }
+ }
+ rd = read(ino_fd, iobuf, sizeof(iobuf));
+ if(rd == -1 && errno == EINTR) {
+ /* This might have been our termination signal! */
+ DBGPRINTF("EINTR received during inotify, restarting poll\n");
+ continue;
+ }
+ if (rd == -1 && errno == EWOULDBLOCK) {
+ continue;
+ }
+ if(rd < 0) {
+ LogError(errno, RS_RET_IO_ERROR, "imfile: error during inotify - ignored");
+ continue;
+ }
+ currev = 0;
+ while(currev < rd) {
+ union {
+ char *buf;
+ struct inotify_event *ev;
+ } savecast;
+ savecast.buf = iobuf+currev;
+ in_dbg_showEv(savecast.ev);
+ in_processEvent(savecast.ev);
+ currev += sizeof(struct inotify_event) + savecast.ev->len;
+ }
+ }
+ }
+
+finalize_it:
+ close(ino_fd);
+ RETiRet;
+}
+
+#else /* #if HAVE_INOTIFY_INIT */
+static rsRetVal
+do_inotify(void)
+{
+ LogError(0, RS_RET_NOT_IMPLEMENTED, "imfile: mode set to inotify, but the "
+ "platform does not support inotify");
+ return RS_RET_NOT_IMPLEMENTED;
+}
+#endif /* #if HAVE_INOTIFY_INIT */
+
+
+/* --- Monitor files in FEN mode (OS_SOLARIS)*/
+#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE) /* use FEN on Solaris! */
+static void
+fen_printevent(int event)
+{
+ if (event & FILE_ACCESS) {
+ DBGPRINTF(" FILE_ACCESS");
+ }
+ if (event & FILE_MODIFIED) {
+ DBGPRINTF(" FILE_MODIFIED");
+ }
+ if (event & FILE_ATTRIB) {
+ DBGPRINTF(" FILE_ATTRIB");
+ }
+ if (event & FILE_DELETE) {
+ DBGPRINTF(" FILE_DELETE");
+ }
+ if (event & FILE_RENAME_TO) {
+ DBGPRINTF(" FILE_RENAME_TO");
+ }
+ if (event & FILE_RENAME_FROM) {
+ DBGPRINTF(" FILE_RENAME_FROM");
+ }
+ if (event & UNMOUNTED) {
+ DBGPRINTF(" UNMOUNTED");
+ }
+ if (event & MOUNTEDOVER) {
+ DBGPRINTF(" MOUNTEDOVER");
+ }
+}
+
+
+/* https://docs.oracle.com/cd/E19253-01/816-5168/port-get-3c/index.html */
+static rsRetVal
+do_fen(void)
+{
+ port_event_t portEvent;
+ struct timespec timeout;
+ DEFiRet;
+
+ /* Set port timeout to 1 second. We need to check for unmonitored files during meantime */
+ // TODO: do we need this timeout at all for equality to old code?
+ // TODO: do we need it to support the timeout feature!
+ timeout.tv_sec = 300;
+ timeout.tv_nsec = 0;
+
+ /* create port instance */
+ if((glport = port_create()) == -1) {
+ LogError(errno, RS_RET_FEN_INIT_FAILED, "do_fen INIT Port failed ");
+ return RS_RET_FEN_INIT_FAILED;
+ }
+
+ do_initial_poll_run();
+
+ DBGPRINTF("do_fen ENTER monitoring loop \n");
+ while(glbl.GetGlobalInputTermState() == 0) {
+ DBGPRINTF("do_fen loop begin... \n");
+ /* Loop through events, if there are any */
+ while (!port_get(glport, &portEvent, &timeout)) { // wie inotify-wait
+ DBGPRINTF("do_fen: received port event with ");
+ fen_printevent((int) portEvent.portev_events);
+ DBGPRINTF("\n");
+ if(portEvent.portev_source != PORT_SOURCE_FILE) {
+ LogError(errno, RS_RET_SYS_ERR, "do_fen: Event from unexpected source "
+ ": %d\n", portEvent.portev_source);
+ continue;
+ }
+ act_obj_t *const act = (act_obj_t*) portEvent.portev_user;
+ DBGPRINTF("do_fen event received: deleted %d, is_file %d, name '%s' foname '%s'\n",
+ act->is_deleted, act->edge->is_file, act->name,
+ ((struct file_obj*)portEvent.portev_object)->fo_name);
+ if(act->is_deleted) {
+ free(act->name);
+ free(act);
+ continue;
+ }
+
+ /* we need to re-associate the object */
+ act->bPortAssociated = 0;
+ fen_setupWatch(act);
+
+ if(act->edge->is_file) {
+ pollFile(act);
+ } else {
+ fs_node_walk(act->edge->node, poll_tree);
+ }
+ }
+ }
+
+ /* close port, will de-activate all file events watches associated
+ * with the port.
+ */
+ close(glport);
+ RETiRet;
+}
+#else /* #if OS_SOLARIS */
+static rsRetVal
+do_fen(void)
+{
+ LogError(0, RS_RET_NOT_IMPLEMENTED, "do_fen: mode set to fen, but the "
+ "platform does not support fen");
+ return RS_RET_NOT_IMPLEMENTED;
+}
+#endif /* #if OS_SOLARIS */
+
+
+/* This function is called by the framework to gather the input. The module stays
+ * most of its lifetime inside this function. It MUST NEVER exit this function. Doing
+ * so would end module processing and rsyslog would NOT reschedule the module. If
+ * you exit from this function, you violate the interface specification!
+ */
+BEGINrunInput
+CODESTARTrunInput
+ #if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE) /* use FEN on Solaris! */
+ if(runModConf->opMode == OPMODE_INOTIFY) {
+ DBGPRINTF("auto-adjusting 'inotify' mode to 'fen' on Solaris\n");
+ runModConf->opMode = OPMODE_FEN;
+ }
+ #endif
+ DBGPRINTF("working in %s mode\n",
+ (runModConf->opMode == OPMODE_POLLING) ? "polling" :
+ ((runModConf->opMode == OPMODE_INOTIFY) ?"inotify" : "fen"));
+ if(runModConf->opMode == OPMODE_POLLING)
+ iRet = doPolling();
+ else if(runModConf->opMode == OPMODE_INOTIFY)
+ iRet = do_inotify();
+ else if(runModConf->opMode == OPMODE_FEN)
+ iRet = do_fen();
+ else {
+ LogError(0, RS_RET_NOT_IMPLEMENTED, "imfile: unknown mode %d set",
+ runModConf->opMode);
+ return RS_RET_NOT_IMPLEMENTED;
+ }
+ DBGPRINTF("terminating upon request of rsyslog core\n");
+ENDrunInput
+
+
+/* The function is called by rsyslog before runInput() is called. It is a last chance
+ * to set up anything specific. Most importantly, it can be used to tell rsyslog if the
+ * input shall run or not. The idea is that if some config settings (or similiar things)
+ * are not OK, the input can tell rsyslog it will not execute. To do so, return
+ * RS_RET_NO_RUN or a specific error code. If RS_RET_OK is returned, rsyslog will
+ * proceed and call the runInput() entry point.
+ */
+BEGINwillRun
+CODESTARTwillRun
+ /* we need to create the inputName property (only once during our lifetime) */
+ CHKiRet(prop.Construct(&pInputName));
+ CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imfile"), sizeof("imfile") - 1));
+ CHKiRet(prop.ConstructFinalize(pInputName));
+finalize_it:
+ENDwillRun
+
+// TODO: refactor this into a generically-usable "atomic file creation" utility for
+// all kinds of "state files"
+static rsRetVal ATTR_NONNULL()
+atomicWriteStateFile(const char *fn, const char *content)
+{
+ DEFiRet;
+ const int fd = open(fn, O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if(fd < 0) {
+ LogError(errno, RS_RET_IO_ERROR, "imfile: cannot open state file '%s' for "
+ "persisting file state - some data will probably be duplicated "
+ "on next startup", fn);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ const size_t toWrite = strlen(content);
+ const ssize_t w = write(fd, content, toWrite);
+ if(w != (ssize_t) toWrite) {
+ LogError(errno, RS_RET_IO_ERROR, "imfile: partial write to state file '%s' "
+ "this may cause trouble in the future. We will try to delete the "
+ "state file, as this provides most consistent state", fn);
+ unlink(fn);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+finalize_it:
+ if(fd >= 0) {
+ close(fd);
+ }
+ RETiRet;
+}
+
+/* This function should be called after any file ID change - that is if
+ * file grown from hash-only statefile, or was truncated, this will ensure
+ * we delete the old file so we do not make garbage in our working dir and
+ * there are no leftover statefiles which can in theory later bind to something
+ * and cause data loss.
+ * jvymazal 2019-11-27
+ */
+static void
+removeOldStatefile(const uchar *statefn, const char *hashToDelete)
+{
+ int ret;
+ uchar statefname[MAXFNAME];
+
+ getFullStateFileName(statefn, hashToDelete, statefname, sizeof(statefname));
+ DBGPRINTF("removing old state file: '%s'\n", statefname);
+ ret = unlink((const char*)statefname);
+ if(ret != 0) {
+ if (errno != ENOENT) {
+ LogError(errno, RS_RET_IO_ERROR,
+ "imfile error trying to delete old state file: '%s' - ignoring this "
+ "error, usually this means a file no longer file is left over, but "
+ "this may also cause some real trouble. Still the best we can do ",
+ statefname);
+ } else {
+ DBGPRINTF("trying to delete no longer valid statefile '%s' which no "
+ "longer exists (probably already deleted)\n", statefname);
+ }
+ }
+}
+
+
+/* This function persists information for a specific file being monitored.
+ * To do so, it simply persists the stream object. We do NOT abort on error
+ * iRet as that makes matters worse (at least we can try persisting the others...).
+ * rgerhards, 2008-02-13
+ */
+static rsRetVal ATTR_NONNULL()
+persistStrmState(act_obj_t *const act)
+{
+ DEFiRet;
+ uchar statefile[MAXFNAME];
+ uchar statefname[MAXFNAME];
+
+ uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
+ getFileID(act);
+ getFullStateFileName(statefn, act->file_id, statefname, sizeof(statefname));
+ DBGPRINTF("persisting state for '%s', state file '%s'\n", act->name, statefname);
+
+ struct json_object *jval = NULL;
+ struct json_object *json = NULL;
+ CHKmalloc(json = json_object_new_object());
+ jval = json_object_new_string((char*) act->name);
+ json_object_object_add(json, "filename", jval);
+ jval = json_object_new_int(strmGetPrevWasNL(act->pStrm));
+ json_object_object_add(json, "prev_was_nl", jval);
+
+ /* we access some data items a bit dirty, as we need to refactor the whole
+ * thing in any case - TODO
+ */
+ jval = json_object_new_int64(act->pStrm->iCurrOffs);
+ json_object_object_add(json, "curr_offs", jval);
+ jval = json_object_new_int64(act->pStrm->strtOffs);
+ json_object_object_add(json, "strt_offs", jval);
+
+ const uchar *const prevLineSegment = strmGetPrevLineSegment(act->pStrm);
+ if(prevLineSegment != NULL) {
+ jval = json_object_new_string((const char*) prevLineSegment);
+ json_object_object_add(json, "prev_line_segment", jval);
+ }
+
+ const uchar *const prevMsgSegment = strmGetPrevMsgSegment(act->pStrm);
+ if(prevMsgSegment != NULL) {
+ jval = json_object_new_string((const char*) prevMsgSegment);
+ json_object_object_add(json, "prev_msg_segment", jval);
+ }
+
+ const char *jstr = json_object_to_json_string_ext(json, JSON_C_TO_STRING_SPACED);
+
+ CHKiRet(atomicWriteStateFile((const char*)statefname, jstr));
+ json_object_put(json);
+
+ /* file-id changed remove the old statefile */
+ if (strncmp((const char *)act->file_id_prev, (const char *)act->file_id, FILE_ID_HASH_SIZE)) {
+ removeOldStatefile(statefn, act->file_id_prev);
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ LogError(0, iRet, "imfile: could not persist state "
+ "file %s - data may be repeated on next "
+ "startup. Is WorkDirectory set?",
+ statefname);
+ }
+
+ RETiRet;
+}
+
+/* This function is called by the framework after runInput() has been terminated. It
+ * shall free any resources and prepare the module for unload.
+ */
+BEGINafterRun
+CODESTARTafterRun
+ if(pInputName != NULL)
+ prop.Destruct(&pInputName);
+ENDafterRun
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURENonCancelInputTermination)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+
+/* The following entry points are defined in module-template.h.
+ * In general, they need to be present, but you do NOT need to provide
+ * any code here.
+ */
+BEGINmodExit
+CODESTARTmodExit
+ /* release objects we used */
+ objRelease(strm, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(ruleset, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+
+ #ifdef HAVE_INOTIFY_INIT
+ free(wdmap);
+ #endif
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_IMOD_QUERIES
+CODEqueryEtryPt_STD_CONF2_QUERIES
+CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES
+CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES
+CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+/* The following function shall reset all configuration variables to their
+ * default values. The code provided in modInit() below registers it to be
+ * called on "$ResetConfigVariables". You may also call it from other places,
+ * but in general this is not necessary. Once runInput() has been called, this
+ * function here is never again called.
+ */
+static rsRetVal
+resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ DEFiRet;
+
+ free(cs.pszFileName);
+ cs.pszFileName = NULL;
+ free(cs.pszFileTag);
+ cs.pszFileTag = NULL;
+ free(cs.pszStateFile);
+ cs.pszStateFile = NULL;
+
+ /* set defaults... */
+ cs.iPollInterval = DFLT_PollInterval;
+ cs.iFacility = 128; /* local0 */
+ cs.iSeverity = 5; /* notice, as of rfc 3164 */
+ cs.readMode = 0;
+ cs.maxLinesAtOnce = 10240;
+ cs.trimLineOverBytes = 0;
+
+ RETiRet;
+}
+
+static inline void
+std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst)
+{
+ LogError(0, NO_ERRCODE, "imfile: ruleset '%s' for %s not found - "
+ "using default ruleset instead", inst->pszBindRuleset,
+ inst->pszFileName);
+}
+
+/* modInit() is called once the module is loaded. It must perform all module-wide
+ * initialization tasks. There are also a number of housekeeping tasks that the
+ * framework requires. These are handled by the macros. Please note that the
+ * complexity of processing is depending on the actual module. However, only
+ * thing absolutely necessary should be done here. Actual app-level processing
+ * is to be performed in runInput(). A good sample of what to do here may be to
+ * set some variable defaults.
+ */
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(strm, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+
+ DBGPRINTF("version %s initializing\n", VERSION);
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilename", 0, eCmdHdlrGetWord,
+ NULL, &cs.pszFileName, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfiletag", 0, eCmdHdlrGetWord,
+ NULL, &cs.pszFileTag, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilestatefile", 0, eCmdHdlrGetWord,
+ NULL, &cs.pszStateFile, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfileseverity", 0, eCmdHdlrSeverity,
+ NULL, &cs.iSeverity, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilefacility", 0, eCmdHdlrFacility,
+ NULL, &cs.iFacility, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilereadmode", 0, eCmdHdlrInt,
+ NULL, &cs.readMode, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilemaxlinesatonce", 0, eCmdHdlrSize,
+ NULL, &cs.maxLinesAtOnce, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfiletrimlineoverbytes", 0, eCmdHdlrSize,
+ NULL, &cs.trimLineOverBytes, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilepersiststateinterval", 0, eCmdHdlrInt,
+ NULL, &cs.iPersistStateInterval, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilebindruleset", 0, eCmdHdlrGetWord,
+ NULL, &cs.pszBindRuleset, STD_LOADABLE_MODULE_ID));
+ /* that command ads a new file! */
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrunfilemonitor", 0, eCmdHdlrGetWord,
+ addInstance, NULL, STD_LOADABLE_MODULE_ID));
+ /* module-global config params - will be disabled in configs that are loaded
+ * via module(...).
+ */
+ CHKiRet(regCfSysLineHdlr2((uchar *)"inputfilepollinterval", 0, eCmdHdlrInt,
+ NULL, &cs.iPollInterval, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler,
+ resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
+ENDmodInit
diff --git a/plugins/imfile/siphash.c b/plugins/imfile/siphash.c
new file mode 100644
index 0000000..8d5fac7
--- /dev/null
+++ b/plugins/imfile/siphash.c
@@ -0,0 +1,185 @@
+/* SipHash reference C implementation
+ *
+ * Copyright (c) 2012-2016 Jean-Philippe Aumasson
+ * <jeanphilippe.aumasson@gmail.com>
+ * Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
+ *
+ * Slightly adapted by rsyslog in regard to build system and code style
+ * check.
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with
+ * this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ *
+ * For details on siphash see https://131002.net/siphash/
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/* default: SipHash-2-4 */
+#define cROUNDS 2
+#define dROUNDS 4
+
+#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
+
+#define U32TO8_LE(p, v) \
+ (p)[0] = (uint8_t)((v)); \
+ (p)[1] = (uint8_t)((v) >> 8); \
+ (p)[2] = (uint8_t)((v) >> 16); \
+ (p)[3] = (uint8_t)((v) >> 24);
+
+#define U64TO8_LE(p, v) \
+ U32TO8_LE((p), (uint32_t)((v))); \
+ U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
+
+#define U8TO64_LE(p) \
+ (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
+ ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
+ ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
+ ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
+
+#define SIPROUND \
+ do { \
+ v0 += v1; \
+ v1 = ROTL(v1, 13); \
+ v1 ^= v0; \
+ v0 = ROTL(v0, 32); \
+ v2 += v3; \
+ v3 = ROTL(v3, 16); \
+ v3 ^= v2; \
+ v0 += v3; \
+ v3 = ROTL(v3, 21); \
+ v3 ^= v0; \
+ v2 += v1; \
+ v1 = ROTL(v1, 17); \
+ v1 ^= v2; \
+ v2 = ROTL(v2, 32); \
+ } while (0)
+
+#ifdef DEBUG
+#define TRACE \
+ do { \
+ printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \
+ (uint32_t)v0); \
+ printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \
+ (uint32_t)v1); \
+ printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \
+ (uint32_t)v2); \
+ printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \
+ (uint32_t)v3); \
+ } while (0)
+#else
+#define TRACE
+#endif
+
+extern int rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
+ uint8_t *out, const size_t outlen); /* avoid compiler warning */
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
+#endif
+int
+#if defined(__clang__)
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+#endif
+rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
+ uint8_t *out, const size_t outlen) {
+
+ uint64_t v0 = 0x736f6d6570736575ULL;
+ uint64_t v1 = 0x646f72616e646f6dULL;
+ uint64_t v2 = 0x6c7967656e657261ULL;
+ uint64_t v3 = 0x7465646279746573ULL;
+ uint64_t k0 = U8TO64_LE(k);
+ uint64_t k1 = U8TO64_LE(k + 8);
+ uint64_t m;
+ int i;
+ const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
+ const int left = inlen & 7;
+ uint64_t b = ((uint64_t)inlen) << 56;
+ assert((outlen == 8) || (outlen == 16));
+ v3 ^= k1;
+ v2 ^= k0;
+ v1 ^= k1;
+ v0 ^= k0;
+
+ if (outlen == 16)
+ v1 ^= 0xee;
+
+ for (; in != end; in += 8) {
+ m = U8TO64_LE(in);
+ v3 ^= m;
+
+ TRACE;
+ for (i = 0; i < cROUNDS; ++i)
+ SIPROUND;
+
+ v0 ^= m;
+ }
+
+ switch (left) {
+ case 7:
+ b |= ((uint64_t)in[6]) << 48;
+ /*FALLTHROUGH*/
+ case 6:
+ b |= ((uint64_t)in[5]) << 40;
+ /*FALLTHROUGH*/
+ case 5:
+ b |= ((uint64_t)in[4]) << 32;
+ /*FALLTHROUGH*/
+ case 4:
+ b |= ((uint64_t)in[3]) << 24;
+ /*FALLTHROUGH*/
+ case 3:
+ b |= ((uint64_t)in[2]) << 16;
+ /*FALLTHROUGH*/
+ case 2:
+ b |= ((uint64_t)in[1]) << 8;
+ /*FALLTHROUGH*/
+ case 1:
+ b |= ((uint64_t)in[0]);
+ break;
+ case 0:
+ default:
+ break;
+ }
+
+ v3 ^= b;
+
+ TRACE;
+ for (i = 0; i < cROUNDS; ++i)
+ SIPROUND;
+
+ v0 ^= b;
+
+ if (outlen == 16)
+ v2 ^= 0xee;
+ else
+ v2 ^= 0xff;
+
+ TRACE;
+ for (i = 0; i < dROUNDS; ++i)
+ SIPROUND;
+
+ b = v0 ^ v1 ^ v2 ^ v3;
+ U64TO8_LE(out, b);
+
+ if (outlen == 8)
+ return 0;
+
+ v1 ^= 0xdd;
+
+ TRACE;
+ for (i = 0; i < dROUNDS; ++i)
+ SIPROUND;
+
+ b = v0 ^ v1 ^ v2 ^ v3;
+ U64TO8_LE(out + 8, b);
+
+ return 0;
+}