diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 694a805b..fbfa6687 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,7 +20,7 @@ jobs: analyzer: off sanitizer: address,undefined - os: macos-14 - packages: automake + packages: autoconf automake libtool build_type: Debug build_tool_options: -j 4 analyzer: off @@ -127,6 +127,6 @@ jobs: run: | set -e -x echo $PATH - autoreconf -i + autoreconf -fi ./configure make -j 2 diff --git a/.gitignore b/.gitignore index 32c9c17c..437f81ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.o +*.lo *.d +/.libs/ +/aclocal.m4 /autom4te.cache /build /config.guess @@ -11,7 +14,11 @@ /config.sub /configure /configure~ +/install-sh +/libtool /libzone.a +/libzone.la +/ltmain.sh /make.dep /.depend /Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index f28d224e..eb352a5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,8 +218,8 @@ if(architecture STREQUAL "x86_64" OR architecture STREQUAL "amd64") unset(CMAKE_REQUIRED_FLAGS) if (HAVE_WESTMERE) set_source_files_properties( - src/westmere/parser.c PROPERTIES COMPILE_FLAGS "-march=westmere") - target_sources(zone PRIVATE src/westmere/parser.c) + src/westmere/wmparser.c PROPERTIES COMPILE_FLAGS "-march=westmere") + target_sources(zone PRIVATE src/westmere/wmparser.c) set_source_files_properties( src/westmere/bench.c PROPERTIES COMPILE_FLAGS "-march=westmere") target_sources(zone-bench PRIVATE src/westmere/bench.c) @@ -233,8 +233,8 @@ if(architecture STREQUAL "x86_64" OR architecture STREQUAL "amd64") unset(CMAKE_REQUIRED_FLAGS) if (HAVE_HASWELL) set_source_files_properties( - src/haswell/parser.c PROPERTIES COMPILE_FLAGS "-march=haswell") - target_sources(zone PRIVATE src/haswell/parser.c) + src/haswell/hwparser.c PROPERTIES COMPILE_FLAGS "-march=haswell") + target_sources(zone PRIVATE src/haswell/hwparser.c) set_source_files_properties( src/haswell/bench.c PROPERTIES COMPILE_FLAGS "-march=haswell") target_sources(zone-bench PRIVATE src/haswell/bench.c) diff --git a/Makefile.in b/Makefile.in index 51aa1bce..f27d07c8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -12,17 +12,25 @@ CC = @CC@ CPPFLAGS = @CPPFLAGS@ -Iinclude -I$(SOURCE)/include -I$(SOURCE)/src -I. CFLAGS = @CFLAGS@ DEPFLAGS = @DEPFLAGS@ +LDFLAGS=@LDFLAGS@ +LIBS=@LIBS@ VPATH = @srcdir@ - +libtool=@libtool@ +LIBTOOL=$(libtool) +srcdir=@srcdir@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ SOURCE = @srcdir@ +version_info=@version_info@ SOURCES = src/zone.c src/fallback/parser.c OBJECTS = $(SOURCES:.c=.o) -WESTMERE_SOURCES = src/westmere/parser.c +WESTMERE_SOURCES = src/westmere/wmparser.c WESTMERE_OBJECTS = $(WESTMERE_SOURCES:.c=.o) -HASWELL_SOURCES = src/haswell/parser.c +HASWELL_SOURCES = src/haswell/hwparser.c HASWELL_OBJECTS = $(HASWELL_SOURCES:.c=.o) NO_OBJECTS = @@ -36,13 +44,13 @@ DEPENDS = $(SOURCES:.c=.d) $(WESTMERE_SOURCES:.c=.d) $(HASWELL_SOURCES:.c=.d) # macros for compatibility. EXPORT_HEADER = include/zone/export.h -.PHONY: all clean +.PHONY: all clean lib -all: libzone.a +all: lib clean: @rm -f .depend - @rm -f libzone.a $(OBJECTS) $(EXPORT_HEADER) + @rm -f libzone.a libzone.la $(OBJECTS) $(EXPORT_HEADER) @rm -f $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) distclean: clean @@ -56,8 +64,22 @@ maintainer-clean: realclean devclean: realclean @rm -rf config.h.in configure -libzone.a: $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) Makefile - $(AR) rcs libzone.a $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) +#libzone.a: $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) Makefile + #$(AR) rcs libzone.a $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) + +libzone.la: $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) Makefile + $(LIBTOOL) --tag=CC --mode=link $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -version-info $(version_info) -no-undefined -export-symbols $(srcdir)/lzonesyms.def -o $@ $(OBJECTS:.o=.lo) $($(WESTMERE)_OBJECTS:.o=.lo) $($(HASWELL)_OBJECTS:.o=.lo) $(LIBS) -rpath $(libdir) + if test -f .libs/libzone.a; then cp .libs/libzone.a .; fi + +libzone.a: libzone.la + +# The copy to the main directory is because that is where (NSD) picks up +# the static library to link to. +# The shared object files are listed to link with. +lib: libzone.la + +list_objs: + @echo $(OBJECTS:.o=.lo) $($(WESTMERE)_OBJECTS:.o=.lo) $($(HASWELL)_OBJECTS:.o=.lo) $(EXPORT_HEADER): @mkdir -p include/zone @@ -65,15 +87,15 @@ $(EXPORT_HEADER): $(WESTMERE_OBJECTS): $(EXPORT_HEADER) .depend Makefile @mkdir -p src/westmere - $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=westmere -o $@ -c $(SOURCE)/$(@:.o=.c) + $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=westmere -o $@ -c $(SOURCE)/$(@:.o=.c) $(HASWELL_OBJECTS): $(EXPORT_HEADER) .depend Makefile @mkdir -p src/haswell - $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=haswell -o $@ -c $(SOURCE)/$(@:.o=.c) + $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=haswell -o $@ -c $(SOURCE)/$(@:.o=.c) $(OBJECTS): $(EXPORT_HEADER) .depend Makefile @mkdir -p src/fallback - $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -o $@ -c $(SOURCE)/$(@:.o=.c) + $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -o $@ -c $(SOURCE)/$(@:.o=.c) @touch $@ .depend: diff --git a/acx_nlnetlabs.m4 b/acx_nlnetlabs.m4 index 6a01dc5a..c8195f5e 100644 --- a/acx_nlnetlabs.m4 +++ b/acx_nlnetlabs.m4 @@ -2,7 +2,10 @@ # Copyright 2009, Wouter Wijngaards, NLnet Labs. # BSD licensed. # -# Version 48 +# Version 50 +# 2025-09-29 add ac_cv_func_malloc_0_nonnull as a cache value for the malloc(0) +# check by ACX_FUNC_MALLOC. +# 2025-09-29 add ACX_CHECK_NONSTRING_ATTRIBUTE, AHX_CONFIG_NONSTRING_ATTRIBUTE. # 2024-01-16 fix to add -l:libssp.a to -lcrypto link check. # and check for getaddrinfo with only header. # 2024-01-15 fix to add crypt32 to -lcrypto link check when checking for gdi32. @@ -71,6 +74,7 @@ # ACX_DEPFLAG - find cc dependency flags. # ACX_DETERMINE_EXT_FLAGS_UNBOUND - find out which flags enable BSD and POSIX. # ACX_CHECK_FORMAT_ATTRIBUTE - find cc printf format syntax. +# ACX_CHECK_NONSTRING_ATTRIBUTE - find cc nonstring attribute syntax. # ACX_CHECK_UNUSED_ATTRIBUTE - find cc variable unused syntax. # ACX_CHECK_FLTO - see if cc supports -flto and use it if so. # ACX_LIBTOOL_C_ONLY - create libtool for C only, improved. @@ -92,6 +96,7 @@ # ACX_FUNC_IOCTLSOCKET - find ioctlsocket, portably. # ACX_FUNC_MALLOC - check malloc, define replacement . # AHX_CONFIG_FORMAT_ATTRIBUTE - config.h text for format. +# AHX_CONFIG_NONSTRING_ATTRIBUTE - config.h text for nonstring. # AHX_CONFIG_UNUSED_ATTRIBUTE - config.h text for unused. # AHX_CONFIG_FSEEKO - define fseeko, ftello fallback. # AHX_CONFIG_RAND_MAX - define RAND_MAX if needed. @@ -490,7 +495,7 @@ AC_DEFUN([AHX_CONFIG_FORMAT_ATTRIBUTE], ]) dnl Check how to mark function arguments as unused. -dnl result in HAVE_ATTR_UNUSED. +dnl result in HAVE_ATTR_UNUSED. dnl Make sure you include AHX_CONFIG_UNUSED_ATTRIBUTE also. AC_DEFUN([ACX_CHECK_UNUSED_ATTRIBUTE], [AC_REQUIRE([AC_PROG_CC]) @@ -525,6 +530,45 @@ if test $ac_cv_c_unused_attribute = yes; then fi ])dnl +dnl Check how to mark function arguments as nonstring. +dnl result in HAVE_ATTR_NONSTRING. +dnl Make sure you include AHX_CONFIG_NONSTRING_ATTRIBUTE also. +AC_DEFUN([ACX_CHECK_NONSTRING_ATTRIBUTE], +[AC_REQUIRE([AC_PROG_CC]) +AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "nonstring" attribute) +AC_CACHE_VAL(ac_cv_c_nonstring_attribute, +[ac_cv_c_nonstring_attribute=no +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +struct test { + char __attribute__((nonstring)) s[1]; +}; +]], [[ + struct test t = { "1" }; + (void) t; +]])],[ac_cv_c_nonstring_attribute="yes"],[ac_cv_c_nonstring_attribute="no"]) +]) + +dnl Setup ATTR_NONSTRING config.h parts. +dnl make sure you call ACX_CHECK_NONSTRING_ATTRIBUTE also. +AC_DEFUN([AHX_CONFIG_NONSTRING_ATTRIBUTE], +[ +#if defined(DOXYGEN) +# define ATTR_NONSTRING(x) x +#elif defined(__cplusplus) +# define ATTR_NONSTRING(x) __attribute__((nonstring)) x +#elif defined(HAVE_ATTR_NONSTRING) +# define ATTR_NONSTRING(x) __attribute__((nonstring)) x +#else /* !HAVE_ATTR_NONSTRING */ +# define ATTR_NONSTRING(x) x +#endif /* !HAVE_ATTR_NONSTRING */ +]) + +AC_MSG_RESULT($ac_cv_c_nonstring_attribute) +if test $ac_cv_c_nonstring_attribute = yes; then + AC_DEFINE(HAVE_ATTR_NONSTRING, 1, [Whether the C compiler accepts the "nonstring" attribute]) +fi +])dnl + dnl Pre-fun for ACX_LIBTOOL_C_ONLY AC_DEFUN([ACX_LIBTOOL_C_PRE], [ # skip these tests, we do not need them. @@ -1190,8 +1234,9 @@ dnl detect malloc and provide malloc compat prototype. dnl $1: unique name for compat code AC_DEFUN([ACX_FUNC_MALLOC], [ - AC_MSG_CHECKING([for GNU libc compatible malloc]) - AC_RUN_IFELSE([AC_LANG_PROGRAM( + AC_CACHE_CHECK([for GNU libc compatible malloc],[ac_cv_func_malloc_0_nonnull], + [ + AC_RUN_IFELSE([AC_LANG_PROGRAM( [[#if defined STDC_HEADERS || defined HAVE_STDLIB_H #include #else @@ -1199,14 +1244,16 @@ char *malloc (); #endif ]], [ if(malloc(0) != 0) return 1;]) ], - [AC_MSG_RESULT([no]) - AC_LIBOBJ(malloc) - AC_DEFINE_UNQUOTED([malloc], [rpl_malloc_$1], [Define if replacement function should be used.])] , - [AC_MSG_RESULT([yes]) - AC_DEFINE([HAVE_MALLOC], 1, [If have GNU libc compatible malloc])], - [AC_MSG_RESULT([no (crosscompile)]) - AC_LIBOBJ(malloc) - AC_DEFINE_UNQUOTED([malloc], [rpl_malloc_$1], [Define if replacement function should be used.])] ) + [ac_cv_func_malloc_0_nonnull=no], + [ac_cv_func_malloc_0_nonnull=yes], + [ac_cv_func_malloc_0_nonnull="no (crosscompile)"]) + ]) + AS_IF([test "$ac_cv_func_malloc_0_nonnull" = yes], + [AC_DEFINE([HAVE_MALLOC], 1, [If have GNU libc compatible malloc])], + [ + AC_LIBOBJ(malloc) + AC_DEFINE_UNQUOTED([malloc], [rpl_malloc_$1], [Define if replacement function should be used.]) + ]) ]) dnl Define fallback for fseeko and ftello if needed. diff --git a/configure.ac b/configure.ac index 72356eda..367ec9c8 100644 --- a/configure.ac +++ b/configure.ac @@ -15,12 +15,52 @@ AC_INIT([simdzone],[0.2.5],[https://github.com/NLnetLabs/simdzone/issues]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) +version_info=0:1:0 +# This is current:revision:age +# 0.2.4 had 0:1:0 +# +# +# Current -- the number of the binary API that we're implementing +# Revision -- which iteration of the implementation of the binary +# API are we supplying? +# Age -- How many previous binary API versions do we also +# support? +# +# If we release a new version that does not change the binary API, +# increment Revision. +# +# If we release a new version that changes the binary API, but does +# not break programs compiled against the old binary API, increment +# Current and Age. Set Revision to 0, since this is the first +# implementation of the new API. +# +# Otherwise, we're changing the binary API and breaking backward +# compatibility with old binaries. Increment Current. Set Age to 0, +# since we're backward compatible with no previous APIs. Set Revision +# to 0 too. +AC_SUBST(version_info) + sinclude(acx_nlnetlabs.m4) m4_include(m4/ax_check_compile_flag.m4) CFLAGS="$CFLAGS" m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_STDC]) +# are we on MinGW? +if uname -s 2>&1 | grep MINGW >/dev/null; then on_mingw="yes" +else + if echo $host | grep mingw >/dev/null; then on_mingw="yes" + else on_mingw="no"; fi +fi +if test "$on_mingw" = "yes"; then + if echo "$host" | $GREP -i -e linux >/dev/null; then + # link with libssp for the stack protector functions on the windows + # compile, for the static compile -l:libssp.a could be used. + # But there is no static option right now for simdzone. + if test -z "$LIBS"; then LIBS="-lssp"; else LIBS="$LIBS -lssp"; fi + fi +fi + # allow user to override the -g -O2 flags. if test "x$CFLAGS" = "x" ; then ACX_CHECK_COMPILER_FLAG(g, [CFLAGS="$CFLAGS -g"]) @@ -28,6 +68,8 @@ ACX_CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"]) ACX_CHECK_PIE fi +ACX_LIBTOOL_C_ONLY + AC_CHECK_HEADERS([endian.h sys/endian.h],,, [AC_INCLUDES_DEFAULT]) AC_CHECK_DECLS([bswap16,bswap32,bswap64], [], [], [ AC_INCLUDES_DEFAULT @@ -140,7 +182,11 @@ int main(int argc, char *argv[]) fi fi -AC_CHECK_FUNCS([realpath],,[AC_MSG_ERROR([realpath is not available])]) +AC_CHECK_FUNCS([realpath],,[ + AC_CHECK_FUNCS([_fullpath],,[ + AC_MSG_ERROR([realpath and _fullpath are not available]) + ]) +]) AC_SUBST([HAVE_ENDIAN_H]) AC_SUBST([HAVE_WESTMERE]) diff --git a/doc/manual/api_reference.rst b/doc/manual/api_reference.rst index 7f6fcda0..08ef217f 100644 --- a/doc/manual/api_reference.rst +++ b/doc/manual/api_reference.rst @@ -17,6 +17,9 @@ Parse functions .. doxygenfunction:: zone_parse_string :project: doxygen +.. doxygenfunction:: zone_parse_from_callback + :project: doxygen + Log priorities -------------- diff --git a/include/zone.h b/include/zone.h index b7e27a8e..df47f80c 100644 --- a/include/zone.h +++ b/include/zone.h @@ -428,6 +428,23 @@ typedef int32_t(*zone_include_t)( const char *, // fully qualified path void *); // user data +/** + * @brief Signature of callback function invoked to read data pieces. + * + * Called to read another piece of the data. Read up to the number of bytes, + * or less bytes up to the end, and signal end of file, or error. + */ +typedef int32_t(*zone_read_data_t)( + zone_parser_t *, + char *, // The data buffer to read in to. + size_t, // The number of bytes to read that is requested. + size_t *, // The number of bytes successfully read, less than the requested + // amount means end-of-file after that. Also 0 means end-of-file + // without data read. Reading bytes or less or 0 at end-of-file + // return with ZONE_SUCCESS. For errors, can pass 0, and return + // with ZONE_READ_ERROR. + void *); // user data + /** * @brief Available configuration options. */ @@ -442,6 +459,12 @@ typedef struct { bool no_includes; /** Maximum $INCLUDE depth. 0 for default. */ uint32_t include_limit; + /** The chroot path. If not NULL, and the $INCLUDE pathname has it as prefix, + * then it is elided from the beginning of the pathname string. Setting it + * to the chroot directory, when chrooted, makes that absolute path names + * continue to work, both before the chroot and after the chroot with + * adjustment. */ + const char* chrootdir; /** Enable 1h2m3s notations for TTLS. */ bool pretty_ttls; /** Origin in wire format. */ @@ -490,6 +513,8 @@ struct zone_parser { /** @private */ zone_options_t options; /** @private */ + zone_read_data_t read_data_callback; + /** @private */ void *user_data; struct { size_t size; @@ -584,6 +609,32 @@ zone_parse_string( void *user_data) zone_nonnull((1,2,3,4)); +/** + * @brief Parse zone using a callback to retrieve data. + * + * Parse using a callback to retrieve data pieces, containing resource + * records in presentation format. The callback is called to return eg. + * the next up-to-N bytes, or up to the end, and signals end of file. + * It is called to read chunks of a couple Kb, until the last (possibly + * partial) chunk, and then end-of-file is returned. + * + * @param[in] parser Zone parser + * @param[in] options Settings used for parsing. + * @param[in] buffers Scratch buffers used by parsing. + * @param[in] callback Callback function that is called to read the data. + * @param[in] user_data Pointer passed verbatim to callbacks. + * + * @returns @ref ZONE_SUCCESS on success or a negative number on error. + */ +ZONE_EXPORT int32_t +zone_parse_from_callback( + zone_parser_t *parser, + const zone_options_t *options, + zone_buffers_t *buffers, + zone_read_data_t callback, + void *user_data) +zone_nonnull((1,2,3,4)); + /** * @defgroup log_priorities Log categories. * diff --git a/lzonesyms.def b/lzonesyms.def new file mode 100644 index 00000000..8bacca0c --- /dev/null +++ b/lzonesyms.def @@ -0,0 +1,4 @@ +zone_log +zone_parse +zone_parse_from_callback +zone_parse_string diff --git a/src/generic/format.h b/src/generic/format.h index 76542c28..5db3c53d 100644 --- a/src/generic/format.h +++ b/src/generic/format.h @@ -254,6 +254,15 @@ static really_inline int32_t parse_dollar_include( file_t *file; if ((code = take_quoted_or_contiguous(parser, &include, &fields[0], token)) < 0) return code; + if (parser->options.chrootdir) { + size_t chrootlen = strlen(parser->options.chrootdir); + if (token->length >= chrootlen && + strncmp(token->data, parser->options.chrootdir, chrootlen) == 0) { + /* Adjust for chroot on include file. */ + token->data += chrootlen; + token->length -= chrootlen; + } + } if ((code = zone_open_file(parser, token->data, token->length, &file)) < 0) return code; diff --git a/src/generic/parser.h b/src/generic/parser.h index 34a90e5d..c7d8b74b 100644 --- a/src/generic/parser.h +++ b/src/generic/parser.h @@ -270,7 +270,7 @@ static int32_t refill(parser_t *parser) if (parser->file->end_of_file) return 0; - assert(parser->file->handle); + assert(parser->file->handle || parser->read_data_callback); // move unread data to start of buffer char *data = parser->file->buffer.data + parser->file->buffer.index; @@ -305,19 +305,35 @@ static int32_t refill(parser_t *parser) parser->file->fields.head[0] = data; } - size_t count = fread( - parser->file->buffer.data + parser->file->buffer.length, - sizeof(parser->file->buffer.data[0]), - parser->file->buffer.size - parser->file->buffer.length, - parser->file->handle); - - if (!count && ferror(parser->file->handle)) - READ_ERROR(parser, "Cannot refill buffer"); + char* readpos; // where to read the data. + size_t count; // number of bytes to read, and read in. + uint8_t is_eof; // if it is at eof. + readpos = parser->file->buffer.data + parser->file->buffer.length; + count = parser->file->buffer.size - parser->file->buffer.length; + is_eof = 0; + + if(parser->read_data_callback) { + /* Read using callback. */ + size_t retcount = 0; + int32_t ret = parser->read_data_callback(parser, readpos, count, + &retcount, parser->user_data); + is_eof = ((retcount == 0) | (retcount < count)); + if(ret < 0) + RAISE_ERROR(parser, ret, "Cannot read data"); + count = retcount; + } else { + /* Read from file. */ + count = fread(readpos, sizeof(parser->file->buffer.data[0]), count, + parser->file->handle); + if (!count && ferror(parser->file->handle)) + READ_ERROR(parser, "Cannot refill buffer"); + is_eof = feof(parser->file->handle) != 0; + } // always null-terminate for terminating token parser->file->buffer.length += (size_t)count; parser->file->buffer.data[parser->file->buffer.length] = '\0'; - parser->file->end_of_file = feof(parser->file->handle) != 0; + parser->file->end_of_file = is_eof; /* After the file, there is padding, that is used by vector instructions, * initialise those bytes. */ diff --git a/src/haswell/parser.c b/src/haswell/hwparser.c similarity index 100% rename from src/haswell/parser.c rename to src/haswell/hwparser.c diff --git a/src/westmere/parser.c b/src/westmere/wmparser.c similarity index 100% rename from src/westmere/parser.c rename to src/westmere/wmparser.c diff --git a/src/zone.c b/src/zone.c index 833af7f6..ff57ed1d 100644 --- a/src/zone.c +++ b/src/zone.c @@ -152,7 +152,8 @@ static void close_file( { assert((file->name == not_a_file) == (file->path == not_a_file)); - const bool is_string = file->name == not_a_file || file->path == not_a_file; + const bool is_string = (file->name == not_a_file || + file->path == not_a_file) && !parser->read_data_callback; assert(!is_string || file == &parser->first); assert(!is_string || file->handle == NULL); @@ -239,7 +240,8 @@ static int32_t open_file( file->path = NULL; if (!(file->name = malloc(length + 1))) return ZONE_OUT_OF_MEMORY; - memcpy(file->name, include, length); + if(length > 0) + memcpy(file->name, include, length); file->name[length] = '\0'; if (!(file->buffer.data = malloc(size))) return (void)close_file(parser, file), ZONE_OUT_OF_MEMORY; @@ -372,6 +374,7 @@ static int32_t initialize_parser( const size_t size = offsetof(parser_t, file); memset(parser, 0, size); parser->options = *options; + parser->read_data_callback = NULL; parser->user_data = user_data; parser->file = &parser->first; parser->buffers.size = buffers->size; @@ -460,6 +463,39 @@ int32_t zone_parse_string( return code; } +int32_t zone_parse_from_callback( + zone_parser_t *parser, + const zone_options_t *options, + zone_buffers_t *buffers, + zone_read_data_t callback, + void *user_data) +{ + int32_t code; + const size_t size = ZONE_WINDOW_SIZE + 1 + ZONE_BLOCK_SIZE; + zone_file_t *file; + + if ((code = initialize_parser(parser, options, buffers, user_data)) < 0) + return code; + parser->read_data_callback = callback; + + file = parser->file; + initialize_file(parser, file); + file->handle = NULL; + if (!(file->buffer.data = malloc(size))) + return (void)close_file(parser, file), ZONE_OUT_OF_MEMORY; + file->buffer.data[0] = '\0'; + file->buffer.size = ZONE_WINDOW_SIZE; + file->buffer.length = 0; + file->buffer.index = 0; + file->end_of_file = 0; + file->fields.tape[0] = &file->buffer.data[0]; + file->fields.tape[1] = &file->buffer.data[0]; + + code = parse(parser, user_data); + zone_close(parser); + return code; +} + zone_nonnull((1,5)) static void print_message( zone_parser_t *parser, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 95815bd9..b973a25c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,7 @@ if(HAVE_HASWELL) set_source_files_properties(haswell/bits.c PROPERTIES COMPILE_FLAGS "-march=haswell") endif() -cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c semantics.c eui.c bounds.c bits.c ttl.c) +cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c semantics.c eui.c bounds.c bits.c ttl.c fromcb.c) set(xbounds ${CMAKE_CURRENT_SOURCE_DIR}/zones/xbounds.zone) set(xbounds_c "${CMAKE_CURRENT_BINARY_DIR}/xbounds.c") diff --git a/tests/fromcb.c b/tests/fromcb.c new file mode 100644 index 00000000..e4ce212a --- /dev/null +++ b/tests/fromcb.c @@ -0,0 +1,272 @@ +/* + * fromcb.c -- test parse from read data callback. + * + * Copyright (c) 2026, NLnet Labs. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#include +#include +#include +#include + +#include "zone.h" + +/* max number of chunks used in the test */ +#define MAX_CHUNKS 16 +/* if test should print verbosely */ +static int verb = 0; + +/* The size of the chunk, if set to this value, makes it use strlen. */ +#define CHUNK_SIZE_STRLEN -3 +/* The size of the chunk, if set to this value, makes it use strlen, and + * pad to the requested size. */ +#define CHUNK_SIZE_STRLENPAD -4 + +/* Chunk of string to return for the test. */ +struct fromcb_chunkinfo { + /* The string with content for the chunk, can be smaller than max len. */ + char* str; + /* the size of the chunk, or the defines for it. */ + int size; + /* the end of the pad, if any. Put on the end of the chunk, if padded. */ + char* padend; + /* the retvalue for the read of the chunk. 0 means success. */ + int32_t retvalue; +}; + +/* The test information for a parse from callback test. Inited to values on + * start, and contains space for counters that are updated. */ +struct fromcb_testinfo { + /* expected return value of parse */ + int32_t code; + /* list of chunks that is read, ends with NULL */ + struct fromcb_chunkinfo chunk[MAX_CHUNKS]; + /* current chunk. */ + size_t chunknum; + /* number of RRs read. */ + size_t num_rrs; + /* expected number of RRs read. */ + size_t expect_num_rrs; +}; + +static int32_t read_data_func( + zone_parser_t * parser, + char *data, + size_t len, + size_t *outlen, + void *user_data) +{ + struct fromcb_testinfo *test = (void *)user_data; + struct fromcb_chunkinfo* chunk; + int32_t retvalue; + size_t chunk_size = 0; + (void)parser; + chunk = &test->chunk[test->chunknum]; + if(chunk->str == NULL) { + *outlen = 0; + if(verb) + fprintf(stderr, "read_data_func(len=%d, outlen=%d) " + "chunk %d returns %d\n", (int)len, (int)*outlen, + (int)test->chunknum, 0); + return 0; + } + + if(chunk->size == CHUNK_SIZE_STRLEN) { + chunk_size = strlen(chunk->str); + if(chunk_size != 0) + memmove(data, chunk->str, chunk_size); + } else if(chunk->size == CHUNK_SIZE_STRLENPAD) { + size_t padendlen = 0; + chunk_size = strlen(chunk->str); + if(chunk_size != 0) + memmove(data, chunk->str, chunk_size); + /* Pad it */ + if(chunk->padend) + padendlen = strlen(chunk->padend); + if(chunk_size + padendlen < len) { + size_t i, padlen = len - chunk_size - padendlen; + char padchar = ' '; + for(i=0; ipadend) { + memmove(data+len-padendlen, chunk->padend, padendlen); + } + chunk_size = len; + } else { + chunk_size = (size_t)chunk->size; + if(chunk_size != 0) + memmove(data, chunk->str, chunk_size); + } + + *outlen = chunk_size; + retvalue = chunk->retvalue; + test->chunknum++; + if(verb) + fprintf(stderr, "read_data_func(len=%d, outlen=%d) chunk %d returns %d\n", + (int)len, (int)*outlen, (int)test->chunknum-1, (int)retvalue); + return retvalue; +} + +static int32_t accept_fromcb( + zone_parser_t *parser, + const zone_name_t *owner, + uint16_t type, + uint16_t class, + uint32_t ttl, + uint16_t rdlength, + const uint8_t *rdata, + void *user_data) +{ + struct fromcb_testinfo *test = (void *)user_data; + (void)parser; + (void)owner; + (void)type; + (void)class; + (void)ttl; + (void)rdlength; + (void)rdata; + + test->num_rrs++; + if(verb) + fprintf(stderr, "accept rr %d / %d\n", + (int)test->num_rrs, (int)test->expect_num_rrs); + return 0; +} + +/*!cmocka */ +void test_fromcb(void **state) +{ + static uint8_t origin[] = + { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; + + static struct fromcb_testinfo tests[] = { + /* fromcb test 0: two chunks */ + { 0, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 2 // num rrs + }, + /* fromcb test 1: one chunk */ + { 0, { + {"www.example.com. IN A 1.2.3.4\n" + "www2.example.com. IN A 1.2.3.4\n" + , CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 2 // num rrs + }, + /* fromcb test 2: four chunks */ + { 0, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www4.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 4 // num rrs + }, + /* fromcb test 3: three chunks, it ends with a full chunk, not partial. */ + { 0, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 3 // num rrs + }, + /* fromcb test 4: three chunks, last has error. */ + { ZONE_READ_ERROR, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, + ZONE_READ_ERROR}, + {NULL, 0, NULL, 0} + }, + 0, 0, 2 // num rrs + }, + /* fromcb test 5: three chunks, last has error, it is memory error. */ + { ZONE_OUT_OF_MEMORY, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, + ZONE_OUT_OF_MEMORY}, + {NULL, 0, NULL, 0} + }, + 0, 0, 2 // num rrs + }, + /* fromcb test 6: first chunk has error. */ + { ZONE_READ_ERROR, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", + ZONE_READ_ERROR}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 0 // num rrs + }, + /* fromcb test 7: four chunks, one RR is spread over two chunks. */ + { 0, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n" + "www-part.example", 0}, + {".com. IN A 1.2.3.4\n" + "www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www4.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 5 // num rrs + }, + /* fromcb test 8: four chunks, syntax error in string. */ + { ZONE_SYNTAX_ERROR, { + {"www.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www2.example.com. syntax-error-here 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www3.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLENPAD, "\n", 0}, + {"www4.example.com. IN A 1.2.3.4\n", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 1 // num rrs + }, + /* fromcb test 9: eof straight away, much like an empty file. */ + { 0, { + {"", CHUNK_SIZE_STRLEN, NULL, 0}, + {NULL, 0, NULL, 0} + }, + 0, 0, 0 // num rrs + } + }; + + (void)state; + + for (size_t i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { + zone_parser_t parser; + zone_name_buffer_t name; + zone_rdata_buffer_t rdata; + zone_buffers_t buffers = { 1, &name, &rdata }; + zone_options_t options; + int32_t code; + struct fromcb_testinfo *test = &tests[i]; + + if(verb) fprintf(stderr, "\n"); + fprintf(stderr, "fromcb test %d\n", (int)i); + + memset(&options, 0, sizeof(options)); + options.accept.callback = accept_fromcb; + options.origin.octets = origin; + options.origin.length = sizeof(origin); + options.default_ttl = 3600; + options.default_class = 1; + + code = zone_parse_from_callback(&parser, &options, &buffers, + read_data_func, (void*)test); + if(verb) + fprintf(stderr, "retcode %d, num_rrs %d\n", (int)code, + (int)test->num_rrs); + assert_int_equal(code, test->code); + assert_int_equal(test->num_rrs, test->expect_num_rrs); + } +} diff --git a/tests/include.c b/tests/include.c index f29b9bd8..27ebd8ca 100644 --- a/tests/include.c +++ b/tests/include.c @@ -721,3 +721,76 @@ diagnostic_pop() free(paths[0]); free(paths[1]); } + +/*!cmocka */ +void include_with_chroot(void **state) +{ + // test parse of $INCLUDE with chrootdir option. + // the chrootdir is set to a prefix, that is then included + // at the front of the $INCLUDE path, without actually chrooting, + // to test it. + const char* chrootdir = "/var/simdtest/"; + // an actual chrootdir may be more "/var/simdtest" and thus leave + // a string starting with '/', which is fine for the chrootdir case. + // for the test it is convenient to remove that beginning '/'. + char *paths[2] = { NULL, NULL }; + FILE *handles[2] = { NULL, NULL }; + struct file_list list = { 0, 2, paths }; + (void)state; + + static const char fmt[] = + "$INCLUDE \"%s%s\"\n" + "example A 192.0.2.1\n"; + +diagnostic_push() +msvc_diagnostic_ignored(4996) + paths[0] = get_tempnam(NULL, "zone"); + assert_non_null(paths[0]); + handles[0] = fopen(paths[0], "wb"); + assert_non_null(handles[0]); + paths[1] = get_tempnam(NULL, "zone"); + assert_non_null(paths[1]); + handles[1] = fopen(paths[1], "wb"); + assert_non_null(handles[1]); +diagnostic_pop() + + fprintf(handles[0], fmt, chrootdir, paths[1]); + (void)fflush(handles[0]); + (void)fclose(handles[0]); + fputs("example A 192.0.2.1\n", handles[1]); + (void)fflush(handles[1]); + (void)fclose(handles[1]); + + char buf[128]; + int len = snprintf(buf, sizeof(buf), fmt, chrootdir, paths[0]); + assert_true(len > 0); + + char *str = calloc((size_t)len + ZONE_BLOCK_SIZE + 1, 1); + assert_non_null(str); + (void)snprintf(str, (size_t)len+1, fmt, chrootdir, paths[0]); + + zone_parser_t parser; + zone_options_t options; + zone_name_buffer_t name; + zone_rdata_buffer_t rdata; + zone_buffers_t buffers = { 1, &name, &rdata }; + + memset(&options, 0, sizeof(options)); + options.include.callback = &track_include_cb; + options.accept.callback = &track_accept_cb; + options.origin.octets = origin; + options.origin.length = sizeof(origin); + options.default_ttl = 3600; + options.default_class = 1; + options.chrootdir = chrootdir; + + int32_t code = zone_parse_string(&parser, &options, &buffers, str, (size_t)len, &list); + remove(paths[0]); + remove(paths[1]); + assert_int_equal(code, ZONE_SUCCESS); + assert_int_equal(list.index, 2); + + free(str); + free(paths[0]); + free(paths[1]); +}