# HG changeset patch # User Olaf Wintermann # Date 1664029570 -7200 # Node ID a1f4cb076d2f50f5bbf3337b0930104c9437576a # Parent 21274e5950af94fc77f6af0a90de16d4367e26fe# Parent f9e9f2b3e2993672b2aadd448fc99d7912a91e19 merge branch webdav into default diff -r 21274e5950af -r a1f4cb076d2f .hgignore --- a/.hgignore Tue Aug 13 22:14:32 2019 +0200 +++ b/.hgignore Sat Sep 24 16:26:10 2022 +0200 @@ -8,3 +8,5 @@ \.rej$ \.conflict\~$ ^nbproject/.*$ +# Default ignored files +.idea/workspace.xml diff -r 21274e5950af -r a1f4cb076d2f Makefile --- a/Makefile Tue Aug 13 22:14:32 2019 +0200 +++ b/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -53,3 +53,5 @@ install: all cd make; $(MAKE) -f install.mk install +install-bin: all + cd make; $(MAKE) -f install.mk install-bin diff -r 21274e5950af -r a1f4cb076d2f configure --- a/configure Tue Aug 13 22:14:32 2019 +0200 +++ b/configure Sat Sep 24 16:26:10 2022 +0200 @@ -57,7 +57,7 @@ --mandir=DIR man documentation [DATAROOTDIR/man] Optional Features: - --enable-pg + --enable-postgresql __EOF__ } @@ -67,40 +67,25 @@ # for ARG in $@ do - if [[ $ARG == --prefix=* ]]; then - PREFIX=${ARG:9} - elif [[ $ARG = --exec-prefix=* ]]; then - EPREFIX=${ARG:14} - elif [[ $ARG = --bindir=* ]]; then - BINDIR=${ARG:9} - elif [[ $ARG = --sbindir=* ]]; then - SBINDIR=${ARG:10} - elif [[ $ARG = --libdir=* ]]; then - LIBDIR=${ARG:9} - elif [[ $ARG = --libexecdir=* ]]; then - LIBEXECDIR=${ARG:13} - elif [[ $ARG = --datadir=* ]]; then - DATADIR=${ARG:10} - elif [[ $ARG = --sysconfdir=* ]]; then - SYSCONFDIR=${ARG:13} - elif [[ $ARG = --sharedstatedir=* ]]; then - SHAREDSTATEDIR=${ARG:17} - elif [[ $ARG = --localstatedir=* ]]; then - LOCALSTATEDIR=${ARG:16} - elif [[ $ARG = --includedir=* ]]; then - INCLUDEDIR=${ARG:12} - elif [[ $ARG = --infodir=* ]]; then - INFODIR=${ARG:10} - elif [[ $ARG = --mandir=* ]]; then - MANDIR=${ARG:9} - elif [ $ARG = "--help" ]; then - printhelp - exit 0 - elif [[ $ARG == --enable-pg ]]; then - FEATURE_PG=on - elif [[ $ARG == --disable-pg ]]; then - unset FEATURE_PG - fi + case "$ARG" in + "--prefix="*) PREFIX=${ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${ARG#--includedir=} ;; + "--infodir="*) INFODIR=${ARG#--infodir=} ;; + "--mandir"*) MANDIR=${ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + "--enable-postgresql") FEATURE_POSTGRESQL=on ;; + "--disable-postgresql") unset FEATURE_POSTGRESQL ;; + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac done # set dir variables @@ -265,6 +250,13 @@ fi CFLAGS="$CFLAGS `$PKG_CONFIG --cflags libpq`" LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs libpq`" + cat >> $TEMP_DIR/make.mk << __EOF__ +# Dependency: libpq +CFLAGS += -DENABLE_POSTGRESQL +PLUGINS += postgresql +TEST_PLUGIN_LDFLAGS += -lwspgtest + +__EOF__ echo yes return 0 done @@ -291,6 +283,14 @@ return 0 done + # dependency openssl + while true + do + LDFLAGS="$LDFLAGS -lssl -lcrypto" + echo yes + return 0 + done + echo no return 1 } @@ -359,7 +359,7 @@ while true do - CFLAGS="$CFLAGS -DBSD" + CFLAGS="$CFLAGS -DBSD -I/usr/local/include" LDFLAGS="$LDFLAGS -lpthread -lm -lldap" cat >> $TEMP_DIR/make.mk << __EOF__ # platform dependend source files @@ -469,6 +469,8 @@ ERROR=1 fi +# Features + echo >> $TEMP_DIR/config.mk if [ ! -z "${CFLAGS}" ]; then @@ -481,10 +483,41 @@ echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk fi +# Target: postgresql +CFLAGS= +LDFLAGS= +CXXFLAGS= + + +# Features +if [ ! -z "$FEATURE_POSTGRESQL" ]; then + # check dependency + dependency_libpq + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $FEATURE_POSTGRESQL != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libpq " + ERROR=1 + fi + fi +fi + + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "POSTGRESQL_CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "POSTGRESQL_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "POSTGRESQL_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + if [ $ERROR -ne 0 ]; then echo echo "Error: Unresolved dependencies" - echo $DEPENCIES_FAILED + echo $DEPENDENCIES_FAILED rm -Rf $TEMP_DIR exit 1 fi diff -r 21274e5950af -r a1f4cb076d2f doc/development/postgresql_vfs.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/development/postgresql_vfs.sql Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,35 @@ +create table Resource ( + resource_id serial primary key, + parent_id int references Resource(resource_id), + nodename text not null, + iscollection boolean not null default false, + + lastmodified timestamp not null default current_date, + creationdate timestamp not null default current_date, + contentlength bigint not null default 0, + + etag uuid, + + resoid oid, + + unique(parent_id, nodename) +); + +create table Property ( + property_id serial primary key, + resource_id int references Resource(resource_id) on delete cascade, + prefix text not null, + xmlns text not null, + pname text not null, + lang text, + nsdeflist text, + pvalue text, + + unique(resource_id, xmlns, pname) +); + +create type property_name as ( + xmlns text, + name text +); + diff -r 21274e5950af -r a1f4cb076d2f doc/development/postgresql_vfs_testdata.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/development/postgresql_vfs_testdata.sql Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,29 @@ + +do $$ +declare + res_id int; +begin + insert into Resource (nodename, iscollection) values ('', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, resoid) values + (res_id, 'file1.txt', (select lo_create(0))), + (res_id, 'file2.txt', (select lo_create(0))), + (res_id, 'file3.txt', (select lo_create(0))), + (res_id, 'file4.txt', (select lo_create(0))); + + insert into Resource(parent_id, nodename, iscollection) values + (res_id, 'dir1', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, iscollection) values + (res_id, 'dir2', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, resoid) values + (res_id, 'd1file1.txt', (select lo_create(0))), + (res_id, 'd2file1.txt', (select lo_create(0))), + (res_id, 'd2file2.txt', (select lo_create(0))); + +end $$; + diff -r 21274e5950af -r a1f4cb076d2f make/configure.vm --- a/make/configure.vm Tue Aug 13 22:14:32 2019 +0200 +++ b/make/configure.vm Sat Sep 24 16:26:10 2022 +0200 @@ -119,46 +119,30 @@ #set( $D = '$' ) for ARG in $@ do - if [[ $ARG == --prefix=* ]]; then - PREFIX=${D}{ARG:9} - elif [[ $ARG = --exec-prefix=* ]]; then - EPREFIX=${D}{ARG:14} - elif [[ $ARG = --bindir=* ]]; then - BINDIR=${D}{ARG:9} - elif [[ $ARG = --sbindir=* ]]; then - SBINDIR=${D}{ARG:10} - elif [[ $ARG = --libdir=* ]]; then - LIBDIR=${D}{ARG:9} - elif [[ $ARG = --libexecdir=* ]]; then - LIBEXECDIR=${D}{ARG:13} - elif [[ $ARG = --datadir=* ]]; then - DATADIR=${D}{ARG:10} - elif [[ $ARG = --sysconfdir=* ]]; then - SYSCONFDIR=${D}{ARG:13} - elif [[ $ARG = --sharedstatedir=* ]]; then - SHAREDSTATEDIR=${D}{ARG:17} - elif [[ $ARG = --localstatedir=* ]]; then - LOCALSTATEDIR=${D}{ARG:16} - elif [[ $ARG = --includedir=* ]]; then - INCLUDEDIR=${D}{ARG:12} - elif [[ $ARG = --infodir=* ]]; then - INFODIR=${D}{ARG:10} - elif [[ $ARG = --mandir=* ]]; then - MANDIR=${D}{ARG:9} - elif [ $ARG = "--help" ]; then - printhelp - exit 0 - #foreach( $opt in $options ) - elif [[ $ARG == --${opt.getArgument()}=* ]]; then - ${opt.getVarName()}=${opt.getArgValue()} + case "$ARG" in + "--prefix="*) PREFIX=${D}{ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${D}{ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${D}{ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${D}{ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${D}{ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${D}{ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${D}{ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${D}{ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${D}{ARG#--includedir=} ;; + "--infodir="*) INFODIR=${D}{ARG#--infodir=} ;; + "--mandir"*) MANDIR=${D}{ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + #foreach( $opt in $options ) + "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;; #end - #foreach( $feature in $features ) - elif [[ $ARG == --enable-${feature.arg} ]]; then - ${feature.getVarName()}=on - elif [[ $ARG == --disable-${feature.arg} ]]; then - unset ${feature.getVarName()} - #end - fi + #foreach( $feature in $features ) + "--enable-${feature.arg}") ${feature.getVarName()}=on ;; + "--disable-${feature.arg}") unset ${feature.getVarName()} ;; + #end + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac done # set dir variables @@ -534,6 +518,23 @@ fi #end +# Features +#foreach( $feature in $target.features ) +if [ ! -z "$${feature.getVarName()}" ]; then +#foreach( $dependency in $feature.dependencies ) + # check dependency + dependency_$dependency + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $${feature.getVarName()} != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 + fi + fi +#end +fi +#end + #foreach( $opt in $target.options ) # Option: --${opt.argument} if [ -z ${D}${opt.getVarName()} ]; then @@ -593,7 +594,7 @@ if [ $ERROR -ne 0 ]; then echo echo "Error: Unresolved dependencies" - echo $DEPENCIES_FAILED + echo $DEPENDENCIES_FAILED rm -Rf $TEMP_DIR exit 1 fi diff -r 21274e5950af -r a1f4cb076d2f make/install.mk --- a/make/install.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/make/install.mk Sat Sep 24 16:26:10 2022 +0200 @@ -30,25 +30,13 @@ include ../config.mk -install: - @echo "install Webserver to $(WS_INSTALL_DIR)" - mkdir -p $(INSTALL_DIR)/bin - mkdir -p $(INSTALL_DIR)/lib - mkdir -p $(INSTALL_DIR)/config - mkdir -p $(INSTALL_DIR)/docs - mkdir -p $(INSTALL_DIR)/logs - mkdir -p $(INSTALL_DIR)/include +install: install-dir install-bin @echo "copy config" cp ../templates/config/init.conf $(INSTALL_DIR)/config/init.conf cp ../templates/config/obj.conf $(INSTALL_DIR)/config/obj.conf cp ../templates/config/mime.types $(INSTALL_DIR)/config/mime.types cp ../templates/config/acl.conf $(INSTALL_DIR)/config/acl.conf sed s:%%WS_HOST%%:$(HOST):g ../templates/config/server.template > $(INSTALL_DIR)/config/server.conf - @echo "copy binaries" - cp ../build/bin/webservd$(APP_EXT) $(INSTALL_DIR)/bin/ - cp ../build/bin/wstool$(APP_EXT) $(INSTALL_DIR)/bin/ - cp ../build/lib/libucx$(LIB_EXT) $(INSTALL_DIR)/lib/ - cp ../build/lib/libwscfg$(LIB_EXT) $(INSTALL_DIR)/lib/ @echo "copy includes" cp ../src/server/public/nsapi.h $(INSTALL_DIR)/include/nsapi.h cp ../src/server/public/auth.h $(INSTALL_DIR)/include/auth.h @@ -64,3 +52,18 @@ chmod +x $(INSTALL_DIR)/bin/reconfig @echo "copy docs" cp -R ../templates/docs $(INSTALL_DIR)/ + +install-dir: + @echo "install Webserver to $(WS_INSTALL_DIR)" + mkdir -p $(INSTALL_DIR)/bin + mkdir -p $(INSTALL_DIR)/lib + mkdir -p $(INSTALL_DIR)/config + mkdir -p $(INSTALL_DIR)/docs + mkdir -p $(INSTALL_DIR)/logs + mkdir -p $(INSTALL_DIR)/include + +install-bin: install-dir + @echo "copy binaries" + cp ../build/bin/webservd$(APP_EXT) $(INSTALL_DIR)/bin/ + cp ../build/bin/wstool$(APP_EXT) $(INSTALL_DIR)/bin/ + cp ../build/lib/* $(INSTALL_DIR)/lib/ diff -r 21274e5950af -r a1f4cb076d2f make/project.xml --- a/make/project.xml Tue Aug 13 22:14:32 2019 +0200 +++ b/make/project.xml Sat Sep 24 16:26:10 2022 +0200 @@ -23,7 +23,7 @@ - -DBSD + -DBSD -I/usr/local/include -lpthread -lm -lldap # platform dependend source files @@ -73,16 +73,28 @@ openssl + + -lssl -lcrypto + + libpq + +CFLAGS += -DENABLE_POSTGRESQL +PLUGINS += postgresql +TEST_PLUGIN_LDFLAGS += -lwspgtest + - + libxml2,openssl + + + + libpq - libxml2,openssl diff -r 21274e5950af -r a1f4cb076d2f make/suncc.mk --- a/make/suncc.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/make/suncc.mk Sat Sep 24 16:26:10 2022 +0200 @@ -2,9 +2,9 @@ # suncc toolchain # -CFLAGS += -xc99 -g -LDFLAGS += -Wl,-R,'$$ORIGIN/../lib' +CFLAGS += -xc99 -g -m64 +LDFLAGS += -m64 -Wl,-R,'$$ORIGIN/../lib' SHLIB_CFLAGS = -Kpic -SHLIB_LDFLAGS = -G +SHLIB_LDFLAGS = -G -m64 diff -r 21274e5950af -r a1f4cb076d2f make/toolchain.sh --- a/make/toolchain.sh Tue Aug 13 22:14:32 2019 +0200 +++ b/make/toolchain.sh Sat Sep 24 16:26:10 2022 +0200 @@ -11,161 +11,171 @@ check_c_compiler() { - cat > $TEMP_DIR/test.c << __EOF__ + cat > $TEMP_DIR/test.c << __EOF__ /* test file */ #include int main(int argc, char **argv) { -#if defined(__GNUC__) - printf("gcc\n"); -#elif defined(__clang__) - printf("clang\n"); +#if defined(__clang__) + printf("clang\n"); +#elif defined(__GNUC__) + printf("gcc\n"); #elif defined(__sun) - printf("suncc\n"); + printf("suncc\n"); #else - printf("unknown\n"); + printf("unknown\n"); #endif - return 0; + return 0; } __EOF__ - rm -f $TEMP_DIR/checkcc - $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null - - if [ $? -ne 0 ]; then - return 1 - fi - return 0 + rm -f $TEMP_DIR/checkcc + $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null + + if [ $? -ne 0 ]; then + return 1 + fi + return 0 } check_cpp_compiler() { - cat > $TEMP_DIR/test.cpp << __EOF__ + cat > $TEMP_DIR/test.cpp << __EOF__ /* test file */ #include int main(int argc, char **argv) { -#if defined(__GNUC__) - std::cout << "gcc" << std::endl; -#elif defined(__clang__) - std::cout << "clang" << std::endl; +#if defined(__clang__) + std::cout << "clang" << std::endl; +#elif defined(__GNUC__) + std::cout << "gcc" << std::endl; #elif defined(__sun) std::cout << "suncc" << std::endl; #else - std::cout << "unknown" << std::endl; + std::cout << "unknown" << std::endl; #endif - return 0; + return 0; } __EOF__ - rm -f $TEMP_DIR/checkcc - $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null - - if [ $? -ne 0 ]; then - return 1 - fi - return 0 + rm -f $TEMP_DIR/checkcc + $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null + + if [ $? -ne 0 ]; then + return 1 + fi + return 0 } printf "detect C compiler... " for COMP in $C_COMPILERS do - check_c_compiler $COMP - if [ $? -ne 0 ]; then - if [ ! -z "$CC" ]; then - if [ $COMP = $CC ]; then - echo "$CC is not a working C Compiler" - TOOLCHAIN_DETECTION_ERROR="error" - break - fi + check_c_compiler $COMP + if [ $? -ne 0 ]; then + if [ ! -z "$CC" ]; then + if [ $COMP = $CC ]; then + echo "$CC is not a working C Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi fi - else - TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + else + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME if [ $COMP = "cc" ]; then - # we have found a working compiler, but in case - # the compiler is gcc or clang, we try to use - # these commands and not 'cc' - TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` - if [ $TOOLCHAIN_NAME = "gcc" ]; then - check_c_compiler "gcc" - if [ $? -eq 0 ]; then - COMP=gcc - fi - fi - if [ $TOOLCHAIN_NAME = "clang" ]; then - check_c_compiler "clang" - if [ $? -eq 0 ]; then - COMP=clang - fi - fi + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_c_compiler "gcc" + if [ $? -eq 0 ]; then + COMP=gcc + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_c_compiler "clang" + if [ $? -eq 0 ]; then + COMP=clang + USE_TOOLCHAIN="clang" + fi + fi fi - TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` - TOOLCHAIN_CC=$COMP - echo $COMP - break - fi + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CC=$COMP + echo $COMP + break + fi done if [ -z $TOOLCHAIN_CC ]; then - echo "not found" + echo "not found" fi printf "detect C++ compiler... " for COMP in $CPP_COMPILERS do - check_cpp_compiler $COMP - if [ $? -ne 0 ]; then + check_cpp_compiler $COMP + if [ $? -ne 0 ]; then if [ ! -z "$CXX" ]; then - if [ $COMP = $CXX ]; then - echo "$CC is not a working C++ Compiler" - TOOLCHAIN_DETECTION_ERROR="error" - break - fi + if [ $COMP = $CXX ]; then + echo "$CC is not a working C++ Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi fi - else - if [ $COMP = "CC" ]; then - # we have found a working compiler, but in case - # the compiler is gcc or clang, we try to use - # these commands and not 'cc' - TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` - if [ $TOOLCHAIN_NAME = "gcc" ]; then - check_cpp_compiler "g++" - if [ $? -eq 0 ]; then - COMP=g++ - fi - fi - if [ $TOOLCHAIN_NAME = "clang" ]; then - check_cpp_compiler "clang++" - if [ $? -eq 0 ]; then - COMP=clang++ - fi - fi + else + if [ $COMP = "CC" ]; then + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_cpp_compiler "g++" + if [ $? -eq 0 ]; then + COMP=g++ + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_cpp_compiler "clang++" + if [ $? -eq 0 ]; then + COMP=clang++ + USE_TOOLCHAIN="clang" + fi + fi fi - TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` - TOOLCHAIN_CXX=$COMP - echo $COMP - break - fi + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CXX=$COMP + echo $COMP + break + fi done if [ -z $TOOLCHAIN_CXX ]; then - echo "not found" + echo "not found" fi TOOLCHAIN_LD=$TOOLCHAIN_CC if [ -z "$TOOLCHAIN_NAME" ]; then - TOOLCHAIN_DETECTION_ERROR="error" + TOOLCHAIN_DETECTION_ERROR="error" else - cat >> $TEMP_DIR/config.mk << __EOF__ + cat >> $TEMP_DIR/config.mk << __EOF__ # toolchain __EOF__ echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk if [ ! -z "$TOOLCHAIN_CXX" ]; then echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk fi - echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk - + echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk + echo >> $TEMP_DIR/config.mk + cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1 - if [ $? -eq 0 ]; then - echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk - fi + if [ $? -eq 0 ]; then + echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk + else + echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk + echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk + fi fi diff -r 21274e5950af -r a1f4cb076d2f src/server/Makefile --- a/src/server/Makefile Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -62,7 +62,7 @@ OBJ_DIRS = daemon safs ucx util webdav config admin plugins test MK_OBJ_DIRS = $(OBJ_DIRS:%=$(OBJ_DIR)server/%) -CFLAGS += -I../ +CFLAGS += -I../ucx/ LDFLAGS += -lucx preparation: $(MK_OBJ_DIRS) @@ -76,8 +76,8 @@ $(LIB_WSCFG): $(CONFOBJS) $(CC) $(SHLIB_LDFLAGS) -o $@ $(CONFOBJS) -$(TEST_TARGET): $(TESTOBJS) - $(CXX) -o $(TEST_TARGET) $(TESTOBJS) -L$(BUILD_ROOT)/build/lib $(LDFLAGS) +$(TEST_TARGET): $(TESTOBJS) $(PLUGINS) + $(CXX) -o $(TEST_TARGET) $(TESTOBJS) -L$(BUILD_ROOT)/build/lib $(LDFLAGS) $(TEST_PLUGIN_LDFLAGS) $(PLUGINS): $(MAIN_TARGET) FORCE diff -r 21274e5950af -r a1f4cb076d2f src/server/admin/admin.c --- a/src/server/admin/admin.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/admin/admin.c Sat Sep 24 16:26:10 2022 +0200 @@ -34,7 +34,6 @@ #include "../util/pblock.h" #include "../config/conf.h" -#include "../config/serverconf.h" #include "../config/objconf.h" static Page *root_page; diff -r 21274e5950af -r a1f4cb076d2f src/server/config/keyfile.c --- a/src/server/config/keyfile.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/config/keyfile.c Sat Sep 24 16:26:10 2022 +0200 @@ -31,7 +31,7 @@ #include "keyfile.h" -KeyfileConfig *load_keyfile_config(char *file) { +KeyfileConfig *load_keyfile_config(const char *file) { FILE *in = fopen(file, "r"); if(in == NULL) { return NULL; @@ -39,7 +39,7 @@ KeyfileConfig *conf = malloc(sizeof(KeyfileConfig)); conf->parser.parse = keyfile_parse; - conf->file = file; + conf->file = strdup(file); conf->users = NULL; int r = cfg_parse_basic_file((ConfigParser*)conf, in); diff -r 21274e5950af -r a1f4cb076d2f src/server/config/keyfile.h --- a/src/server/config/keyfile.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/config/keyfile.h Sat Sep 24 16:26:10 2022 +0200 @@ -50,7 +50,7 @@ size_t numgroups; } KeyfileEntry; -KeyfileConfig *load_keyfile_config(char *file); +KeyfileConfig *load_keyfile_config(const char *file); void free_keyfile_config(KeyfileConfig *conf); int keyfile_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line); diff -r 21274e5950af -r a1f4cb076d2f src/server/config/mimeconf.c --- a/src/server/config/mimeconf.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/config/mimeconf.c Sat Sep 24 16:26:10 2022 +0200 @@ -38,7 +38,7 @@ void *ptr; } ucx_regdestr; -MimeConfig *load_mime_config(char *file) { +MimeConfig *load_mime_config(const char *file) { FILE *in = fopen(file, "r"); if(in == NULL) { return NULL; @@ -46,7 +46,6 @@ MimeConfig *conf = malloc(sizeof(MimeConfig)); conf->parser.parse = mimeconf_parse; - conf->file = file; conf->directives = NULL; conf->ntypes = 0; int r = cfg_parse_basic_file((ConfigParser*)conf, in); diff -r 21274e5950af -r a1f4cb076d2f src/server/config/mimeconf.h --- a/src/server/config/mimeconf.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/config/mimeconf.h Sat Sep 24 16:26:10 2022 +0200 @@ -37,7 +37,6 @@ typedef struct _mime_conf { ConfigParser parser; - char *file; UcxList *directives; // MimeDirective list int ntypes; } MimeConfig; @@ -47,7 +46,7 @@ UcxList *exts; // char* } MimeDirective; -MimeConfig *load_mime_config(char *file); +MimeConfig *load_mime_config(const char *file); void free_mime_config(MimeConfig *conf); diff -r 21274e5950af -r a1f4cb076d2f src/server/config/objs.mk --- a/src/server/config/objs.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/config/objs.mk Sat Sep 24 16:26:10 2022 +0200 @@ -33,10 +33,10 @@ CONFOBJ = objconf.o CONFOBJ += conf.o CONFOBJ += initconf.o -CONFOBJ += serverconf.o CONFOBJ += mimeconf.o CONFOBJ += acl.o CONFOBJ += keyfile.o +CONFOBJ += serverconfig.o CONFOBJS = $(CONFOBJ:%=$(CONF_OBJPRE)%) CONFSOURCE = $(CONFOBJ:%.o=config/%.c) diff -r 21274e5950af -r a1f4cb076d2f src/server/config/serverconf.c --- a/src/server/config/serverconf.c Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2013 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -#include "serverconf.h" - -ServerConfig *load_server_config(char *file) { - FILE *in = fopen(file, "r"); - if(in == NULL) { - return NULL; - } - - ServerConfig *conf = malloc(sizeof(ServerConfig)); - conf->parser.parse = serverconf_parse; - conf->file = file; - conf->obj = NULL; - - conf->objects = ucx_map_new(16); - int r = cfg_parse_basic_file((ConfigParser*)conf, in); - cfg_map_destr(conf->parser.mp->pool, conf->objects); - if(r != 0) { - // TODO: free - return NULL; - } - - fclose(in); - - return conf; -} - -void free_server_config(ServerConfig *conf) { - ucx_mempool_destroy(conf->parser.mp->pool); - free(conf); -} - -int serverconf_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line){ - ServerConfig *conf = p; - UcxAllocator *mp = conf->parser.mp; - - begin->type = cfg_get_line_type(line); - switch(begin->type) { - case LINE_BEGIN_TAG: { - ConfigTag *tag = cfg_parse_begin_tag(line, conf->parser.mp); - - // create server config object - ServerConfigObject *obj = OBJ_NEW( - conf->parser.mp, - ServerConfigObject); - obj->type = tag->name; - obj->begin = begin; - obj->end = end; - obj->directives = NULL; - - // add object to server config - UcxList *list = ucx_map_sstr_get(conf->objects, obj->type); - list = ucx_list_append_a(mp, list, obj); - ucx_map_sstr_put(conf->objects, obj->type, list); - conf->obj = obj; - - break; - } - case LINE_END_TAG: { - sstr_t tag = cfg_get_end_tag_name(line); - if(sstrcmp(tag, conf->obj->type) != 0) { - log_ereport(LOG_FAILURE, "server.conf: syntax error: wrong close tag"); - log_ereport(LOG_FAILURE, "open tag: %s close tag: %s", sstrdup(tag).ptr, sstrdup(conf->obj->type).ptr); - exit(-1); - } - conf->obj = NULL; - - break; - } - case LINE_DIRECTIVE: { - if(conf->obj == NULL) { - log_ereport(LOG_FAILURE, "server.conf: directive outside of object"); - exit(-1); - } - - ConfigDirective *d = cfg_parse_directive(line, conf->parser.mp); - d->begin = begin; - d->end = end; - - //printf("%s.%s\n", conf->obj->type.ptr, d->directive_type.ptr); - conf->obj->directives = ucx_list_append_a(mp, conf->obj->directives, d); - } - } - return 0; -} - - -UcxList* srvcfg_get_listeners(ServerConfig *cfg, UcxAllocator *mp, int *error) { - mp = mp ? mp : cfg->parser.mp; - - UcxList *list = ucx_map_sstr_get(cfg->objects, sstrn("Listener", 8)); - UcxList *lslist = NULL; - UCX_FOREACH(elm, list) { - ServerConfigObject *ls = elm->data; - sstr_t name = cfg_directivelist_get_str(ls->directives, sstr("Name")); - sstr_t port = cfg_directivelist_get_str(ls->directives, sstr("Port")); - sstr_t vs = cfg_directivelist_get_str( - ls->directives, - sstr("DefaultVS")); - sstr_t threadpool = cfg_directivelist_get_str( - ls->directives, - sstr("Threadpool")); - - CfgListener *listener = OBJ_NEW_N(mp, CfgListener); - // threadpool is optional, all other configs must be set - if(!name.ptr || !port.ptr || !vs.ptr) { - // TODO: log error - *error = 1; - listener->cfg_correct = 0; - } else { - listener->cfg_correct = 1; - } - - if(name.ptr) { - listener->name = sstrdup_a(mp, name); - } - if(port.ptr) { - // don't expect that port is null terminated, sstrdup it to be sure - sstr_t portdp = sstrdup(port); - listener->port = atoi(portdp.ptr); - free(portdp.ptr); - } - if(vs.ptr) { - listener->vs = sstrdup_a(mp, vs); - } - if(threadpool.ptr) { - listener->threadpool = sstrdup_a(mp, threadpool); - } - - lslist = ucx_list_append_a(mp, lslist, listener); - } - - return lslist; -} diff -r 21274e5950af -r a1f4cb076d2f src/server/config/serverconf.h --- a/src/server/config/serverconf.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2013 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef SERVERCONF_H -#define SERVERCONF_H - -#include "conf.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _server_conf_obj { - ConfigLine *begin; - ConfigLine *end; - - sstr_t type; - UcxList *directives; -} ServerConfigObject; - -typedef struct _server_conf { - ConfigParser parser; - char *file; - UcxMap *objects; // contains UcxList of ServerConfigObject - // parser temp vars - ServerConfigObject *obj; -} ServerConfig; - -// server.conf objects - -typedef struct _cfg_listener { - ServerConfigObject *cfgobj; - sstr_t name; - sstr_t vs; - sstr_t threadpool; - sstr_t address; - int port; - int nacceptors; - int cfg_correct; -} CfgListener; - -typedef struct _cfg_keyfile_authdb { - sstr_t file; -} CfgKeyfileAuthDB; - -typedef struct _cfg_ldap_authdb { - sstr_t host; - int port; - sstr_t basedn; - sstr_t binddn; - sstr_t bindpw; -} CfgLDAPAuthDB; - -union authdb { - CfgKeyfileAuthDB keyfile; - CfgLDAPAuthDB ldap; -}; - -enum authdb_type { - AUTHDB_TYPE_KEYFILE, - AUTHDB_TYPE_LDAP -}; - -typedef struct _cfg_authdb { - sstr_t name; - enum authdb_type type; - union authdb cfg; - int cfg_correct; -} CfgAuthDB; - - -/* - * Loads and parses a server configuration file. The function only structures - * the file content to configuration objects with directives. The semantics - * of the objects and directives can be extracted with the srvcfg_* funcsions. - */ -ServerConfig *load_server_config(char *file); - -/* - * frees the ServerConfig object - */ -void free_server_config(ServerConfig *conf); - -// private - parses a server.conf line -int serverconf_parse(void *p, ConfigLine *begin, ConfigLine *end, sstr_t line); - - -UcxList* srvcfg_get_listeners(ServerConfig *cfg, UcxAllocator *mp, int *error); - - -#ifdef __cplusplus -} -#endif - -#endif /* SERVERCONF_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/server/config/serverconfig.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/config/serverconfig.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,361 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "serverconfig.h" + +#include +#include +#include +#include + +#include +#include + +ServerConfig* serverconfig_load(const char *file) { + FILE *in = fopen(file, "r"); + if(in == NULL) { + return NULL; + } + + UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + if(!buf) { + fclose(in); + return NULL; + } + + ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write); + fclose(in); + + ServerConfig *scfg = serverconfig_parse(scstrn(buf->space, buf->size)); + + ucx_buffer_free(buf); + return scfg; +} + + +static CFGToken get_next_token(scstr_t content, int *pos) { + CFGToken token = { {NULL, 0}, CFG_NO_TOKEN }; + CFGTokenType type = CFG_TOKEN; + + int start = *pos; + + int token_begin = -1; + int token_end = content.length-1; + + int quote = 0; + int comment = 0; + + int i; + char prev = 0; + for(i=start;i= 0) { + token_end = i; + break; + } + } else if(c == '"') { + quote = 1; + if(token_begin < 0) { + token_begin = i; + } + } else if(token_begin < 0) { + token_begin = i; + } + prev = c; + } + + *pos = i + 1; + + if(token_begin < 0) { + return token; // error + } + + token.type = type; + token.content = scstrsubsl(content, token_begin, token_end - token_begin); + return token; +} + +/* +static void test_print_config(ConfigNode *parent) { + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + + if(node->type == CONFIG_NODE_SPACE) { + printf("sp: %s", node->text_begin.ptr); + } else if(node->type == CONFIG_NODE_COMMENT) { + printf("cm: %s", node->text_begin.ptr); + } else if(node->type == CONFIG_NODE_OBJECT) { + printf("o{: %s : %s", node->name.ptr, node->text_begin.ptr); + test_print_config(node); + printf("o}: %s", node->text_end.ptr); + } else if(node->type == CONFIG_NODE_DIRECTIVE) { + printf("di: %s", node->text_begin.ptr); + } else { + printf("fk: %s", node->text_begin.ptr); + } + } +} +*/ + +static void config_arg_set_value(UcxAllocator *a, ConfigArg *arg, CFGToken token) { + scstr_t nv = scstrchr(token.content, '='); + if(!nv.ptr) { + arg->value = sstrdup_a(a, token.content); + } else { + intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr); + scstr_t name = token.content; + name.length = (size_t)eq; + + scstr_t value = nv; + value.ptr++; + value.length--; + if(value.length > 1 && value.ptr[0] == '"' && value.ptr[value.length-1] == '"') { + value.ptr++; + value.length -= 2; // remove quote + } + + arg->name = sstrdup_a(a, name); + arg->value = sstrdup_a(a, value); + } +} + +ServerConfig* serverconfig_parse(scstr_t content) { + UcxMempool *mp = ucx_mempool_new(512); + if(!mp) return NULL; + UcxAllocator *a = mp->allocator; + + ServerConfig *config = ucx_mempool_malloc(mp, sizeof(ServerConfig)); + if(!config) { + ucx_mempool_destroy(mp); + return NULL; + } + config->mp = mp; + + // PARSE: + // first non space/comment token is directive/object name + // following tokens are arguments + // newline starts new directive + // '{' converts directive to object and following directives will + // be placed into the object + int pos = 0; // needed for tokenizer + CFGToken token; + + ConfigNode *root_obj = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + root_obj->type = CONFIG_NODE_OBJECT; + + UcxList *node_stack = ucx_list_prepend(NULL, root_obj); + + ConfigNode *current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + current->type = CONFIG_NODE_SPACE; + ConfigNode *obj = NULL; + int obj_closed = 0; + + int text_start = 0; + int err = 0; + while((token = get_next_token(content, &pos)).type != CFG_NO_TOKEN) { + //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr); + + switch(token.type) { + case CFG_NO_TOKEN: break; + case CFG_TOKEN_COMMENT: { + if(current->type == CONFIG_NODE_SPACE) { + current->type = CONFIG_NODE_COMMENT; + } + break; + } + case CFG_TOKEN_SPACE: break; + case CFG_TOKEN_NEWLINE: { + scstr_t line = scstrsubsl(content, text_start, pos - text_start); + text_start = pos; + + sstr_t line_cp = sstrdup_a(a, line); + + ConfigNode *parent = node_stack->data; + if(current->type == CONFIG_NODE_CLOSE_OBJECT) { + parent->text_end = line_cp; + node_stack = ucx_list_remove_a(a, node_stack, node_stack); + } else if(current->type == CONFIG_NODE_OPEN_OBJECT) { + sstr_t new_textbegin = sstrcat_a(a, 2, obj->text_begin, line_cp); + alfree(a, obj->text_begin.ptr); + alfree(a, line_cp.ptr); + obj->text_begin = new_textbegin; + } else { + current->text_begin = line_cp; + ConfigNode *parent = node_stack->data; + parent->children = ucx_list_append_a(a, parent->children, current); + } + + if(obj && obj->type == CONFIG_NODE_OBJECT) { + node_stack = ucx_list_prepend_a(a, node_stack, obj); + obj = NULL; + } + + current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode)); + current->type = CONFIG_NODE_SPACE; + + obj_closed = 0; + break; + } + case CFG_TOKEN: { + if(!sstrcmp(token.content, S("{"))) { + if(!obj) { + err = 1; + break; + } + obj->type = CONFIG_NODE_OBJECT; + if(current != obj) { + current->type = CONFIG_NODE_OPEN_OBJECT; + } + } else if(!sstrcmp(token.content, S("}"))) { + obj_closed = 1; // force newline before next directive + obj = NULL; + current->type = CONFIG_NODE_CLOSE_OBJECT; + } else { + if(obj_closed) { + err = 1; + break; + } + + if(!current->name.ptr) { + current->name = sstrdup_a(a, token.content); + current->type = CONFIG_NODE_DIRECTIVE; + obj = current; + } else { + ConfigArg *arg = ucx_mempool_calloc(mp, 1, sizeof(ConfigArg)); + config_arg_set_value(a, arg, token); + current->args = ucx_list_append_a(a, current->args, arg); + } + } + break; + } + } + + if(err) { + break; + } + } + + if(pos < content.length || err) { + // content not fully parsed because of an error + ucx_mempool_destroy(mp); + return NULL; + } + + //test_print_config(&root_obj); + config->root = root_obj; + config->tab = sstrdup_a(a, SC("\t")); + + return config; +} + +void serverconfig_free(ServerConfig *cfg) { + ucx_mempool_destroy(cfg->mp); +} + +ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name) { + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + if(node->type == type && !sstrcasecmp(node->name, name)) { + return node; + } + } + return NULL; +} + +UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name) { + UcxList *nodes = NULL; + + UCX_FOREACH(elm, parent->children) { + ConfigNode *node = elm->data; + if(node->type == type && !sstrcasecmp(node->name, name)) { + nodes = ucx_list_append(nodes, node); + } + } + + return nodes; +} + +scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name) { + ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name); + if(node && ucx_list_size(node->args) == 1) { + ConfigArg *arg = node->args->data; + return SCSTR(arg->value); + } + return scstrn(NULL, 0); +} + +sstr_t serverconfig_arg_name_value(UcxAllocator *a, scstr_t str, scstr_t *name) { + int valstart = 0; + for(int i=0;ifree(user); } @@ -316,7 +317,7 @@ uid_t owner, gid_t owninggroup); -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) { +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) { sstr_t p; if(path[0] != '/') { size_t n = 128; @@ -331,11 +332,11 @@ } } sstr_t wd = sstr(cwd); - sstr_t pp = sstr(path); + sstr_t pp = sstr((char*)path); p = sstrcat(3, wd, sstrn("/", 1), pp); } else { - p = sstrdup(sstr(path)); + p = sstrdup(sstr((char*)path)); } if(p.ptr[p.length-1] == '/') { p.ptr[p.length-1] = 0; @@ -461,6 +462,11 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + // TODO: + return 1; +} + int solaris_acl_check( char *path, struct stat *s, @@ -571,6 +577,10 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + return 1; +} + void fs_acl_finish() { } @@ -579,7 +589,11 @@ #ifdef BSD -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) { +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) { + return 1; +} + +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { return 1; } @@ -594,7 +608,7 @@ #include -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask) { +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask) { struct passwd *ws_pw = conf_getglobals()->Vuserpw; if(!ws_pw) { log_ereport(LOG_FAILURE, "fs_acl_check: unknown webserver uid/gid"); @@ -638,6 +652,11 @@ return 1; } +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask) { + // TODO + return 1; +} + void fs_acl_finish() { struct passwd *pw = conf_getglobals()->Vuserpw; if(!pw) { diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/acl.h --- a/src/server/daemon/acl.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/acl.h Sat Sep 24 16:26:10 2022 +0200 @@ -48,7 +48,8 @@ // file system acl functions -int fs_acl_check(SysACL *acl, User *user, char *path, uint32_t access_mask); +int fs_acl_check(SysACL *acl, User *user, const char *path, uint32_t access_mask); +int fs_acl_check_fd(SysACL *acl, User *user, int fd, uint32_t access_mask); void fs_acl_finish(); #ifdef __cplusplus diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/acldata.c --- a/src/server/daemon/acldata.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/acldata.c Sat Sep 24 16:26:10 2022 +0200 @@ -32,39 +32,20 @@ #include "acldata.h" -ACLData* acl_data_new() { - ACLData *dat = malloc(sizeof(ACLData)); - dat->ref = 1; +ACLData* acl_data_new(UcxAllocator *a) { + ACLData *dat = almalloc(a, sizeof(ACLData)); + if(!dat) { + return NULL; + } - dat->namedACLs = ucx_map_new(16); + dat->namedACLs = ucx_map_new_a(a, 16); + if(!dat->namedACLs) { + return NULL; + } return dat; } -void acl_data_ref(ACLData *acldata) { - if(acldata) { - ws_atomic_inc32(&acldata->ref); - } -} - -void acl_data_unref(ACLData *acldata) { - uint32_t ref = ws_atomic_dec32(&acldata->ref); - if(ref == 0) { - UcxMapIterator i = ucx_map_iterator(acldata->namedACLs); - WSAcl *acl; - UCX_MAP_FOREACH(key, acl, i) { - free(acl->ace); - free(acl->ece); - if(acl->acl.authprompt) { - free(acl->acl.authprompt); - } - free(acl); - } - ucx_map_free(acldata->namedACLs); - free(acldata); - } -} - ACLList* acl_get(ACLData *acldata, char *name) { ACLList *acl = ucx_map_cstr_get(acldata->namedACLs, name); return acl; diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/acldata.h --- a/src/server/daemon/acldata.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/acldata.h Sat Sep 24 16:26:10 2022 +0200 @@ -41,12 +41,9 @@ typedef struct acl_data { UcxMap *namedACLs; - uint32_t ref; } ACLData; ACLData* acl_data_new(); -void acl_data_ref(ACLData *acldata); -void acl_data_unref(ACLData *acldata); ACLList* acl_get(ACLData *acldata, char *name); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/auth.h --- a/src/server/daemon/auth.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/auth.h Sat Sep 24 16:26:10 2022 +0200 @@ -33,6 +33,8 @@ #include #include "../public/auth.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -48,12 +50,12 @@ typedef struct user_cache_elm UserCacheElm; struct user_cache_elm { - CachedUser *user; - UserCacheElm *next_user; // next elm in the cached user list - UcxKey key; // key to access this element - size_t slot; // slot in the map - UserCacheElm *next_elm; // next element in this map slot - time_t created; + CachedUser *user; + UserCacheElm *next_user; // next elm in the cached user list + UcxKey key; // key to access this element + size_t slot; // slot in the map + UserCacheElm *next_elm; // next element in this map slot + time_t created; }; typedef struct { diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/config.c --- a/src/server/daemon/config.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/config.c Sat Sep 24 16:26:10 2022 +0200 @@ -48,6 +48,7 @@ #include "threadpools.h" #include "ldap_auth.h" #include "configmanager.h" +#include "resourcepool.h" #include "vserver.h" #include "../util/pblock.h" @@ -55,10 +56,13 @@ #include "../util/atomic.h" #include "ucx/buffer.h" -pool_handle_t *cfg_pool; +pool_handle_t *init_pool; -// TODO: Funktion für ConfigDirective -> directive -// TODO: Funktion für UcxList parameter list -> pblock +char* cfg_config_file_path(const char *file) { + sstr_t base = ST("config/"); + sstr_t path = sstrcat(2, base, scstr(file)); + return path.ptr; +} int load_init_conf(char *file) { log_ereport(LOG_VERBOSE, "load_init_conf"); @@ -70,14 +74,14 @@ } UcxAllocator *mp = cfg->parser.mp; - cfg_pool = pool_create(); // one pool for one Configuration + init_pool = pool_create(); // one pool for one Configuration UcxList *dirs = cfg->directives; while(dirs != NULL) { ConfigDirective *dir = dirs->data; /* create NSAPI directive */ directive *d = malloc(sizeof(directive)); - d->param = pblock_create_pool(cfg_pool, 8); + d->param = pblock_create_pool(init_pool, 8); UcxList *param = cfg_param_list(dir->value, mp); while(param != NULL) { ConfigParam *p = param->data; @@ -127,19 +131,34 @@ return 0; } -ServerConfiguration* load_server_conf(ServerConfiguration *old, char *file) { +ServerConfiguration* load_server_conf(char *file) { log_ereport(LOG_VERBOSE, "load_server_conf"); - ServerConfig *serverconf = load_server_config(file); - if(serverconf == NULL) { + ServerConfig *serverconf = serverconfig_load(file); + if(!serverconf) { log_ereport(LOG_FAILURE, "Cannot load server.conf"); + return NULL; } - ServerConfiguration *serverconfig = calloc(1, sizeof(ServerConfiguration)); + + pool_handle_t *pool = pool_create(); + + + ServerConfiguration *serverconfig = pool_calloc(pool, 1, sizeof(ServerConfiguration)); serverconfig->ref = 1; - serverconfig->pool = pool_create(); + serverconfig->pool = pool; + + UcxAllocator allocator = util_pool_allocator(serverconfig->pool); + serverconfig->a = pool_malloc(pool, sizeof(UcxAllocator)); + *serverconfig->a = allocator; + serverconfig->listeners = NULL; - serverconfig->host_vs = ucx_map_new(16); - serverconfig->authdbs = ucx_map_new(16); + serverconfig->host_vs = ucx_map_new_a(&allocator, 16); + serverconfig->authdbs = ucx_map_new_a(&allocator, 16); + serverconfig->resources = ucx_map_new_a(&allocator, 16); + serverconfig->dav = ucx_map_new_a(&allocator, 16); + + + // TODO: init serverconfig stuff @@ -161,49 +180,54 @@ */ // init logfile first - UcxList *lfl = ucx_map_sstr_get(serverconf->objects, sstrn("LogFile", 7)); - if(lfl != NULL) { - ServerConfigObject *logobj = lfl->data; - if(logobj == NULL) { + UcxList *list = NULL; + + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("LogFile")); + if(list) { + ConfigNode *logobj = list->data; + if(!logobj) { // error - return NULL; + return NULL; // TODO: fix memory leak } int ret = cfg_handle_logfile(serverconfig, logobj); if(ret != 0) { // cannot initialize log file - return NULL; + return NULL; // TODO: fix memory leak } } else { // horrible error return NULL; } + ucx_list_free(list); - UcxList *list = ucx_map_sstr_get(serverconf->objects, sstrn("Runtime", 7)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Runtime")); UCX_FOREACH(elm, list) { - ServerConfigObject *scfgobj = elm->data; - if(cfg_handle_runtime(serverconfig, scfgobj)) { + ConfigNode *runtimeobj = elm->data; + if(cfg_handle_runtime(serverconfig, runtimeobj)) { // error return NULL; } } + ucx_list_free(list); - list = ucx_map_sstr_get(serverconf->objects, sstrn("Threadpool", 10)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Threadpool")); UCX_FOREACH(elm, list) { if(cfg_handle_threadpool(serverconfig, elm->data)) { return NULL; } } + ucx_list_free(list); // check thread pool config if(check_thread_pool_cfg() != 0) { /* critical error */ return NULL; } - list = ucx_map_sstr_get(serverconf->objects, sstrn("EventHandler", 12)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("EventHandler")); UCX_FOREACH(elm, list) { if(cfg_handle_eventhandler( - serverconfig, (ServerConfigObject*)elm->data)) { + serverconfig, elm->data)) { // error return NULL; } @@ -213,39 +237,59 @@ /* critical error */ return NULL; } + ucx_list_free(list); - list = ucx_map_sstr_get(serverconf->objects, sstrn("AccessLog", 9)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("AccessLog")); UCX_FOREACH(elm, list) { - ServerConfigObject *scfgobj = elm->data; + ConfigNode *scfgobj = elm->data; if(cfg_handle_accesslog(serverconfig, scfgobj)) { return NULL; } } + ucx_list_free(list); - list = ucx_map_sstr_get(serverconf->objects, sstrn("AuthDB", 6)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("AuthDB")); + UCX_FOREACH(elm, list) { + ConfigNode *scfgobj = elm->data; + if(cfg_handle_authdb(serverconfig, scfgobj)) { + return NULL; + } + } + ucx_list_free(list); + + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Listener")); UCX_FOREACH(elm, list) { - ServerConfigObject *scfgobj = elm->data; - if(cfg_handle_authdb(serverconfig, scfgobj)) { + ConfigNode *scfgobj = elm->data; + if(cfg_handle_listener(serverconfig, scfgobj)) { + return NULL; + } + } + ucx_list_free(list); + + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("VirtualServer")); + UCX_FOREACH(elm, list) { + ConfigNode *scfgobj = elm->data; + if(cfg_handle_vs(serverconfig, scfgobj)) { + return NULL; + } + } + ucx_list_free(list); + + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("ResourcePool")); + UCX_FOREACH(elm, list) { + ConfigNode *scfgobj = elm->data; + if(cfg_handle_resourcepool(serverconfig, scfgobj)) { return NULL; } } - list = ucx_map_sstr_get(serverconf->objects, sstrn("Listener", 8)); + list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Dav")); UCX_FOREACH(elm, list) { - ServerConfigObject *scfgobj = elm->data; - if(cfg_handle_listener(serverconfig, scfgobj)) { + ConfigNode *scfgobj = elm->data; + if(cfg_handle_dav(serverconfig, scfgobj)) { return NULL; } } - - list = ucx_map_sstr_get(serverconf->objects, sstrn("VirtualServer", 13)); - UCX_FOREACH(elm, list) { - ServerConfigObject *scfgobj = elm->data; - if(cfg_handle_vs(serverconfig, scfgobj)) { - return NULL; - } - } - // set VirtualServer for all listeners UcxList *ls = serverconfig->listeners; @@ -268,7 +312,8 @@ ls = ls->next; } - free_server_config(serverconf); + serverconfig_free(serverconf); + return serverconfig; } @@ -279,8 +324,7 @@ void cfg_unref(ServerConfiguration *cfg) { uint32_t ref = ws_atomic_dec32(&cfg->ref); if(ref == 0) { - // TODO: free configuration - printf("free ServerConfiguration %"PRIxPTR"\n", (intptr_t)cfg); + pool_destroy(cfg->pool); } } @@ -289,53 +333,36 @@ } -int cfg_handle_runtime(ServerConfiguration *cfg, ServerConfigObject *obj) { - sstr_t user = cfg_directivelist_get_str(obj->directives, sstr("User")); +int cfg_handle_runtime(ServerConfiguration *cfg, ConfigNode *obj) { + scstr_t user = serverconfig_directive_value(obj, SC("User")); if(user.ptr) { - cfg->user = sstrdup_pool(cfg->pool, user); + cfg->user = sstrdup_a(cfg->a, user); } - sstr_t tmp = cfg_directivelist_get_str(obj->directives, sstr("Temp")); + scstr_t tmp = serverconfig_directive_value(obj, SC("Temp")); if(tmp.ptr) { - cfg->tmp = sstrdup_pool(cfg->pool, tmp); + cfg->tmp = sstrdup_a(cfg->a, tmp); } else { + // TODO: do this check after all config loading is done log_ereport(LOG_MISCONFIG, "no temporary directory specified"); return -1; } // mime file - sstr_t mf = cfg_directivelist_get_str(obj->directives, sstr("MimeFile")); - sstr_t base = sstr("config/"); + scstr_t mf = serverconfig_directive_value(obj, SC("MimeFile")); + scstr_t base = SC("config/"); sstr_t file = sstrcat(2, base, mf); - ConfigFile *f = cfgmgr_get_file(file); - if(f == NULL) { - f = malloc(sizeof(ConfigFile)); - f->data = NULL; - f->file = sstrdup(file); - f->reload = mime_conf_reload; - f->last_modified = 0; - - // load the file content - //f->reload(f, cfg); - if(cfgmgr_reload_file(f, cfg, NULL)) { - free(f->file.ptr); - free(f); - - free(file.ptr); - return -1; - } - cfgmgr_attach_file(f); + if(mime_conf_load(cfg, file)) { + return -1; } - cfg->mimetypes = f->data; - free(file.ptr); return 0; } -int cfg_handle_logfile(ServerConfiguration *cfg, ServerConfigObject *obj) { - sstr_t file = cfg_directivelist_get_str(obj->directives, sstr("File")); - sstr_t lvl = cfg_directivelist_get_str(obj->directives, sstr("Level")); +int cfg_handle_logfile(ServerConfiguration *cfg, ConfigNode *obj) { + scstr_t file = serverconfig_directive_value(obj, SC("File")); + scstr_t lvl = serverconfig_directive_value(obj, SC("Level")); if(file.ptr == NULL || lvl.ptr == NULL) { /* missing log file parameters */ @@ -343,21 +370,18 @@ } LogConfig logcfg; - logcfg.file = sstrdup(file).ptr; - logcfg.level = sstrdup(lvl).ptr; + logcfg.file = file.ptr; + logcfg.level = lvl.ptr; logcfg.log_stdout = 0; logcfg.log_stderr = 0; /* TODO: stdout, stderr config */ int ret = init_log_file(&logcfg); - - free(logcfg.file); - free(logcfg.level); return ret; } -int cfg_handle_threadpool(ServerConfiguration *cfg, ServerConfigObject *obj) { +int cfg_handle_threadpool(ServerConfiguration *cfg, ConfigNode *obj) { ThreadPoolConfig poolcfg; poolcfg.min_threads = 4; poolcfg.min_threads = 4; @@ -365,21 +389,11 @@ poolcfg.queue_size = 64; poolcfg.stack_size = 262144; - sstr_t name = cfg_directivelist_get_str( - obj->directives, - sstr("Name")); - sstr_t min = cfg_directivelist_get_str( - obj->directives, - sstr("MinThreads")); - sstr_t max = cfg_directivelist_get_str( - obj->directives, - sstr("MaxThreads")); - sstr_t stack = cfg_directivelist_get_str( - obj->directives, - sstr("StackSize")); - sstr_t queue = cfg_directivelist_get_str( - obj->directives, - sstr("QueueSize")); + scstr_t name = serverconfig_directive_value(obj, SC("Name")); + scstr_t min = serverconfig_directive_value(obj, SC("MinThreads")); + scstr_t max = serverconfig_directive_value(obj, SC("MaxThreads")); + scstr_t stack = serverconfig_directive_value(obj, SC("StackSize")); + scstr_t queue = serverconfig_directive_value(obj, SC("QueueSize")); // TODO: Type if(name.length == 0) { @@ -388,27 +402,41 @@ } if(min.length != 0) { - min = sstrdup(min); - poolcfg.min_threads = atoi(min.ptr); - free(min.ptr); + int64_t value; + if(util_strtoint(min.ptr, &value)) { + poolcfg.min_threads = value; + } else { + log_ereport(LOG_MISCONFIG, "Threadpool: MinThreads not an integer"); + return 1; + } } if(max.length != 0) { - max = sstrdup(max); - poolcfg.max_threads = atoi(max.ptr); - free(max.ptr); + int64_t value; + if(util_strtoint(max.ptr, &value)) { + poolcfg.max_threads = value; + } else { + log_ereport(LOG_MISCONFIG, "Threadpool: MaxThreads not an integer"); + return 1; + } } if(stack.length != 0) { - stack = sstrdup(stack); - poolcfg.stack_size = atoi(stack.ptr); - free(stack.ptr); + int64_t value; + if(util_strtoint(stack.ptr, &value)) { + poolcfg.stack_size = value; + } else { + log_ereport(LOG_MISCONFIG, "Threadpool: StackSize not an integer"); + } } if(queue.length != 0) { - queue = sstrdup(queue); - poolcfg.queue_size = atoi(queue.ptr); - free(queue.ptr); + int64_t value; + if(util_strtoint(queue.ptr, &value)) { + poolcfg.queue_size = value; + } else { + log_ereport(LOG_MISCONFIG, "Threadpool: QueueSize not an integer"); + } } create_threadpool(name, &poolcfg); @@ -416,32 +444,49 @@ return 0; } -int cfg_handle_eventhandler(ServerConfiguration *c, ServerConfigObject *obj) { +#define EV_MAX_THREADS 2048 +int cfg_handle_eventhandler(ServerConfiguration *c, ConfigNode *obj) { EventHandlerConfig evcfg; - sstr_t name = cfg_directivelist_get_str(obj->directives, sstr("Name")); - sstr_t threads = cfg_directivelist_get_str( - obj->directives, - sstr("Threads")); - sstr_t isdefault = cfg_directivelist_get_str( - obj->directives, - sstr("Default")); + scstr_t name = serverconfig_directive_value(obj, SC("Name")); + scstr_t threads = serverconfig_directive_value(obj, SC("Threads")); + scstr_t isdefault = serverconfig_directive_value(obj, SC("Default")); evcfg.name = name; - sstr_t s = sstrdup(threads); - evcfg.nthreads = atoi(s.ptr); - free(s.ptr); + int64_t value; + if(!util_strtoint(threads.ptr, &value)) { + log_ereport(LOG_MISCONFIG, "EventHandler: Threads: '%s' is not an integer", threads.ptr); + return 1; + } + if(value < 1 || value > EV_MAX_THREADS) { + log_ereport(LOG_MISCONFIG, "EventHandler: Invalid number of threads (1 .. %d)", EV_MAX_THREADS); + return 1; + } + + evcfg.nthreads = value; evcfg.isdefault = util_getboolean(isdefault.ptr, 0); return create_event_handler(&evcfg); } -int cfg_handle_accesslog(ServerConfiguration *cfg, ServerConfigObject *obj) { +int cfg_handle_resourcepool(ServerConfiguration *cfg, ConfigNode *obj) { + scstr_t name = serverconfig_directive_value(obj, SC("Name")); + scstr_t type = serverconfig_directive_value(obj, SC("Type")); + + int ret = 0; + if(resourcepool_new(cfg, type, name, obj)) { + ret = 1; + } + + return ret; +} + +int cfg_handle_accesslog(ServerConfiguration *cfg, ConfigNode *obj) { // TODO: use a name to identify the log file - sstr_t file = cfg_directivelist_get_str(obj->directives, sstr("File")); + scstr_t file = serverconfig_directive_value(obj, SC("File")); if(file.ptr == NULL) { return 0; } @@ -456,10 +501,10 @@ return 0; } AccessLog *log = pool_malloc(cfg->pool, sizeof(AccessLog)); - log->file = sstrdup_pool(cfg->pool, file); + log->file = sstrdup_a(cfg->a, file); log->format = format; log->log = log_file; - cfg->logfiles = ucx_list_append(cfg->logfiles, log); + cfg->logfiles = ucx_list_append_a(cfg->a, cfg->logfiles, log); if(!cfg->default_log) { cfg->default_log = log; @@ -468,61 +513,31 @@ return 0; } -int cfg_handle_authdb(ServerConfiguration *cfg, ServerConfigObject *obj) { - sstr_t name = cfg_directivelist_get_str(obj->directives, sstr("Name")); - sstr_t type = cfg_directivelist_get_str(obj->directives, sstr("Type")); +int cfg_handle_authdb(ServerConfiguration *cfg, ConfigNode *obj) { + scstr_t name = serverconfig_directive_value(obj, SC("Name")); + scstr_t type = serverconfig_directive_value(obj, SC("Type")); + + AuthDB *authdb = NULL; if(!sstrcmp(type, sstr("ldap"))) { LDAPConfig conf; - sstr_t host = cfg_directivelist_get_str( - obj->directives, - sstr("Host")); - sstr_t port = cfg_directivelist_get_str( - obj->directives, - sstr("Port")); - sstr_t basedn = cfg_directivelist_get_str( - obj->directives, - sstr("BaseDN")); - sstr_t binddn = cfg_directivelist_get_str( - obj->directives, - sstr("BindDN")); - sstr_t basepw = cfg_directivelist_get_str( - obj->directives, - sstr("BindPW")); - - host = sstrdup(host); - port = sstrdup(port); - basedn = sstrdup(basedn); - binddn = sstrdup(binddn); - basepw = sstrdup(basepw); + scstr_t host = serverconfig_directive_value(obj, SC("Host")); + scstr_t port = serverconfig_directive_value( obj, SC("Port")); + scstr_t basedn = serverconfig_directive_value(obj, SC("BaseDN")); + scstr_t binddn = serverconfig_directive_value(obj, SC("BindDN")); + scstr_t basepw = serverconfig_directive_value(obj, SC("BindPW")); - conf.hostname = host.ptr; + conf.hostname = sstrdup_a(cfg->a, host).ptr; conf.port = atoi(port.ptr); - conf.basedn = basedn.ptr; - conf.binddn = binddn.ptr; - conf.bindpw = basepw.ptr; - - name = sstrdup(name); - - AuthDB *authdb = create_ldap_authdb(name.ptr, &conf); - ucx_map_sstr_put(cfg->authdbs, name, authdb); + conf.basedn = sstrdup_a(cfg->a, basedn).ptr; + conf.binddn = sstrdup_a(cfg->a, binddn).ptr; + conf.bindpw = sstrdup_a(cfg->a, basepw).ptr; - // TODO: create_ldap_authdb should copy the strings - /* - free(host.ptr); - free(port.ptr); - free(basedn.ptr); - free(binddn.ptr); - free(basepw.ptr); - free(name.ptr); - */ - + authdb = create_ldap_authdb(cfg, name.ptr, &conf); } else if(!sstrcmp(type, sstr("keyfile"))) { // we only need the file parameter - sstr_t file = cfg_directivelist_get_str( - obj->directives, - sstr("File")); + scstr_t file = serverconfig_directive_value(obj, SC("File")); if(file.length == 0) { log_ereport( LOG_MISCONFIG, @@ -531,67 +546,55 @@ } // load keyfile - ConfigFile *f = cfgmgr_get_file(file); - if(f == NULL) { - f = malloc(sizeof(ConfigFile)); - f->data = NULL; - f->file = sstrdup(file); - f->reload = keyfile_reload; - f->last_modified = 0; - //f->reload(f, cfg); - if(cfgmgr_reload_file(f, cfg, NULL)) { - free(f->file.ptr); - free(f); - return -1; - } - cfgmgr_attach_file(f); - } - - // add keyfile authdb - Keyfile *keyfile = f->data; - keyfile->authdb.name = sstrdup(name).ptr; - ucx_map_sstr_put(cfg->authdbs, name, keyfile); + authdb = keyfile_load(cfg, file); } + if(authdb) { + if(ucx_map_sstr_put(cfg->authdbs, name, authdb)) { + return -1; + } + } + return 0; } -int cfg_handle_listener(ServerConfiguration *cfg, ServerConfigObject *obj) { +int cfg_handle_listener(ServerConfiguration *cfg, ConfigNode *obj) { ListenerConfig lc; ZERO(&lc, sizeof(ListenerConfig)); lc.cfg = cfg; lc.port = 8080; lc.nacceptors = 1; + scstr_t name = serverconfig_directive_value(obj, SC("Name")); + scstr_t port = serverconfig_directive_value(obj, SC("Port")); + scstr_t vs = serverconfig_directive_value(obj, SC("DefaultVS")); + scstr_t thrp = serverconfig_directive_value(obj, SC("Threadpool")); + scstr_t blck = serverconfig_directive_value(obj, SC("BlockingIO")); + // TODO: use sstrdup_pool? - lc.name = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("Name"))); - lc.port = atoi(cfg_directivelist_get_str( - obj->directives, - sstr("Port")).ptr); - lc.vs = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("DefaultVS"))); - lc.threadpool = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("Threadpool"))); - - sstr_t blockingio = cfg_directivelist_get_str( - obj->directives, - sstr("BlockingIO")); - if(blockingio.ptr) { - lc.blockingio = util_getboolean_s(blockingio, WS_FALSE); + int64_t port_value; + if(!util_strtoint(port.ptr, &port_value)) { + log_ereport(LOG_MISCONFIG, "Listener: Invalid argument for parameter 'Port': '%s'", port.ptr); + return 1; + } + if(port_value < 1 || port_value > 65535) { + log_ereport(LOG_MISCONFIG, "Listener: Port number out of range (1 .. 65535)"); + return 1; } - sstr_t ssl = cfg_directivelist_get_str(obj->directives, S("SSL")); + lc.name = sstrdup(name); + lc.port = port_value; + lc.vs = sstrdup(vs); + lc.threadpool = sstrdup(thrp); + + lc.blockingio = util_getboolean_s(blck, WS_FALSE); + + scstr_t ssl = serverconfig_directive_value(obj, SC("SSL")); if(util_getboolean_s(ssl, WS_FALSE)) { - sstr_t cert = cfg_directivelist_get_str(obj->directives, S("Cert")); - sstr_t privkey = cfg_directivelist_get_str(obj->directives, S("Key")); - sstr_t chain = cfg_directivelist_get_str(obj->directives, S("CertChain")); - sstr_t disableprot = cfg_directivelist_get_str( - obj->directives, - S("SSLDisableProtocol")); + scstr_t cert = serverconfig_directive_value(obj, SC("Cert")); + scstr_t privkey = serverconfig_directive_value(obj, SC("Key")); + scstr_t chain = serverconfig_directive_value(obj, SC("CertChain")); + scstr_t disableprot = serverconfig_directive_value(obj, SC("SSLDisableProtocol")); WSBool config_ok = WS_TRUE; // TODO: log error @@ -628,84 +631,50 @@ return 1; } - listener->default_vs.vs_name = lc.vs.ptr; - cfg->listeners = ucx_list_append(cfg->listeners, listener); + listener->default_vs.vs_name = sstrdup_a(cfg->a, lc.vs).ptr; + cfg->listeners = ucx_list_append_a(cfg->a, cfg->listeners, listener); return 0; } -int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj) { +int cfg_handle_vs(ServerConfiguration *cfg, ConfigNode *obj) { VirtualServer *vs = vs_new(); - vs->name = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("Name"))); - vs->host = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("Host"))); - vs->document_root = sstrdup(cfg_directivelist_get_str( - obj->directives, - sstr("DocRoot"))); - sstr_t objfile = cfg_directivelist_get_str( - obj->directives, - sstr("ObjectFile")); - sstr_t aclfile = cfg_directivelist_get_str( - obj->directives, - sstr("ACLFile")); + vs->name = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("Name"))); + vs->host = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("Host"))); + vs->document_root = sstrdup_a(cfg->a, serverconfig_directive_value(obj, SC("DocRoot"))); + + scstr_t objfile = serverconfig_directive_value(obj, SC("ObjectFile")); + scstr_t aclfile = serverconfig_directive_value(obj, SC("ACLFile")); // load the object config file sstr_t base = sstr("config/"); sstr_t file = sstrcat(2, base, objfile); - file = sstrcat(2, base, objfile); + // sstrcat with allocator because we want to keep the string + file = sstrcat_a(cfg->a, 2, base, objfile); - // the file is managed by the configuration manager - ConfigFile *f = cfgmgr_get_file(file); - if(f == NULL) { - f = malloc(sizeof(ConfigFile)); - f->data = NULL; - f->file = sstrdup(file); - f->reload = object_conf_reload; - f->last_modified = 0; - //f->reload(f, cfg); - if(cfgmgr_reload_file(f, cfg, NULL)) { - free(f->file.ptr); - free(f); - - free(file.ptr); - return -1; - } - cfgmgr_attach_file(f); + HTTPObjectConfig *httpobj = objconf_load(cfg, file); + if(!httpobj) { + return -1; } - vs->objectfile = sstrdup(file); - vs->objects = (HTTPObjectConfig*)f->data; - free(file.ptr); + vs->objectfile = file; + vs->objects = httpobj; // load acl config file file = sstrcat(2, base, aclfile); - ConfigFile *aclf = cfgmgr_get_file(file); - if(aclf == NULL) { - aclf = malloc(sizeof(ConfigFile)); - aclf->data = NULL; - aclf->file = sstrdup(file); - aclf->reload = acl_conf_reload; - aclf->last_modified = 0; - //aclf->reload(aclf, cfg); - if(cfgmgr_reload_file(aclf, cfg, NULL)) { - free(aclf->file.ptr); - free(aclf); - - free(file.ptr); - return -1; - } - cfgmgr_attach_file(aclf); + ACLData *acldata = acl_conf_load(cfg, file); + if(!acldata) { + return -1; } - vs->acls = aclf->data; + vs->acls = acldata; + free(file.ptr); + // set the access log for the virtual server - // TODO: don't use always the default + // TODO: don't always use the default vs->log = cfg->default_log; ucx_map_sstr_put(cfg->host_vs, vs->host, vs); @@ -713,54 +682,130 @@ return 0; } - -int object_conf_reload(ConfigFile *file, ServerConfiguration *cfg) { - HTTPObjectConfig *old_conf = file->data; - file->data = load_obj_conf(file->file.ptr); - if(old_conf) { - object_conf_unref(old_conf); +int cfg_handle_dav(ServerConfiguration *cfg, ConfigNode *obj) { + UcxList *backends = NULL; // list of ConfigArg* + UcxAllocator a = util_pool_allocator(cfg->pool); + int init_error; + + // parse args + char *uri = NULL; + char *ppath = NULL; + char *name = NULL; + UCX_FOREACH(elm, obj->args) { + ConfigArg *arg = elm->data; + if(arg->name.ptr == NULL) { + // default: uri + uri = arg->value.ptr; + } else if(!sstrcasecmp(arg->name, SC("uri"))) { + uri = arg->value.ptr; + } else if(!sstrcasecmp(arg->name, SC("ppath"))) { + ppath = arg->value.ptr; + } else if(!sstrcasecmp(arg->name, SC("name"))) { + name = arg->value.ptr; + } } - if(file->data) { - return 0; - } else { + if(!uri && !ppath && !name) { return 1; } -} - -void object_conf_ref(HTTPObjectConfig *conf) { - if(conf) { - ws_atomic_inc32(&conf->ref); + + // get a list of all DavBackends + UCX_FOREACH(elm, obj->children) { + ConfigNode *node = elm->data; + if(!sstrcasecmp(node->name, SC("DavBackend"))) { + if(node->type == CONFIG_NODE_DIRECTIVE) { + if(ucx_list_size(node->args) == 1) { + ConfigArg *arg = node->args->data; + backends = ucx_list_append(backends, arg); + } else { + log_ereport(LOG_MISCONFIG, "DavBackend must have only one value"); + ucx_list_free(backends); + return 1; + } + } else { + log_ereport(LOG_MISCONFIG, "DavBackend must be a directive"); + ucx_list_free(backends); + return 1; + } + } } + + int ret = 0; + WebdavRepository *repository = pool_malloc(cfg->pool, sizeof(WebdavRepository)); + repository->vfs = NULL; + repository->vfsInitData = NULL; + repository->davBackends = NULL; + + // initialize backends + UCX_FOREACH(elm, backends) { + // the DavBackend value should contain the dav class name + ConfigArg *backendArg = elm->data; + + WebdavType *dav = webdav_get_type((scstr_t){backendArg->value.ptr, backendArg->value.length}); + if(!dav) { + log_ereport(LOG_MISCONFIG, "Unknown webdav backend type '%s'", backendArg->value.ptr); + ret = 1; + break; + } + + // call backend init + // init_data can be NULL, errors will be indicated by init_error + void *init_data = webdav_init_backend(cfg, cfg->pool, dav, obj, &init_error); + if(init_error) { + log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s", backendArg->value.ptr); + ret = 1; + break; + } + + WebdavBackendInitData *davInit = pool_malloc(cfg->pool, sizeof(WebdavBackendInitData)); + if(!davInit) { + log_ereport(LOG_FAILURE, "Failed to initialize webdav backend %s: OOM", backendArg->value.ptr); + ret = 1; + break; + } + davInit->davType = dav; + davInit->davInitData = init_data; + + repository->davBackends = ucx_list_append_a(&a, repository->davBackends, davInit); + } + + // initialize vfs + scstr_t vfs_class = serverconfig_directive_value(obj, SC("VFS")); + if(vfs_class.length > 0) { + VfsType *vfs = vfs_get_type((scstr_t){vfs_class.ptr, vfs_class.length}); + if(vfs) { + repository->vfs = vfs; + repository->vfsInitData = vfs_init_backend(cfg, cfg->pool, vfs, obj, &init_error); + ret = init_error; + } else { + log_ereport(LOG_FAILURE, "Unknown vfs type '%s'", vfs_class.ptr); + ret = 1; + } + } + + scstr_t object = serverconfig_directive_value(obj, SC("Object")); + if(object.length > 0) { + repository->object = sstrdup_a(&a, object); + if(repository->object.length != object.length) { + // OOM + log_ereport(LOG_FAILURE, "Cannot create webdav repository: OOM"); + ret = 1; + } + } + + if(!ret) { + if(name) { + ucx_map_cstr_put(cfg->dav, name, repository); + } else { + log_ereport(LOG_FAILURE, "TODO: location based dav repositories not implemented"); + ret = 1; + } + } + + return ret; } -void object_conf_unref(HTTPObjectConfig *conf) { - uint32_t ref = ws_atomic_dec32(&conf->ref); - if(ref == 0) { - printf("free HTTPObjectConfig %"PRIxPTR"\n", (intptr_t)conf); - pool_destroy(conf->pool); - } -} - -HTTPObjectConfig* load_obj_conf(char *file) { - log_ereport(LOG_VERBOSE, "load_obj_conf"); - - // new conf function test - ObjectConfig *cfg = load_object_config(file); - UcxAllocator *mp = cfg->parser.mp; - if(cfg == NULL) { - return NULL; - } - - // create object config - pool_handle_t *pool = pool_create(); - HTTPObjectConfig *conf = pool_calloc(pool, sizeof(HTTPObjectConfig), 1); - conf->pool = pool; - - // convert ObjectConfig to HTTPObjectConfig - - // add objects - conf->nobj = ucx_list_size(cfg->objects); - conf->objects = pool_calloc(pool, conf->nobj, sizeof(httpd_object*)); +static int convert_objconf(ServerConfiguration *scfg, ObjectConfig *cfg, HTTPObjectConfig *conf, sstr_t file) { + pool_handle_t *pool = conf->pool; UcxList *objlist = cfg->objects; int i = 0; @@ -772,13 +817,16 @@ char *ppath = NULL; if(cob->name.length > 0) { name = sstrdup_pool(pool, cob->name).ptr; + if(!name) return -1; } if(cob->ppath.length > 0) { ppath = sstrdup_pool(pool, cob->ppath).ptr; + if(!ppath) return -1; } // create and add object httpd_object *obj = object_new(pool, name); + if(!obj) return -1; obj->path = NULL; conf->objects[i] = obj; @@ -788,8 +836,9 @@ UcxList *dirs = cob->directives[j]; while(dirs != NULL) { ConfigDirective *cfgdir = dirs->data; - + directive *d = pool_malloc(pool, sizeof(directive)); + if(!d) return -1; if(cfgdir->condition) { sstr_t expr = cfgdir->condition->param_str; d->cond = condition_from_str(pool, expr.ptr, expr.length); @@ -799,7 +848,7 @@ d->param = pblock_create_pool(pool, 8); // add params - UcxList *param = cfg_param_list(cfgdir->value, mp); + UcxList *param = cfg_param_list(cfgdir->value, scfg->a); while(param != NULL) { ConfigParam *p = param->data; pblock_nvlinsert( @@ -814,13 +863,13 @@ // get function char *func_name = pblock_findval("fn", d->param); if(!func_name) { - log_ereport(LOG_MISCONFIG, "%s: Missing fn parameter", file); - return NULL; + log_ereport(LOG_MISCONFIG, "%s: Missing fn parameter", file.ptr); + return -1; } d->func = get_function(func_name); if(!d->func) { log_ereport(LOG_MISCONFIG, "func %s not found", func_name); - return NULL; + return -1; } dirs = dirs->next; @@ -834,66 +883,94 @@ i++; objlist = objlist->next; } + + return 0; +} + +HTTPObjectConfig* objconf_load(ServerConfiguration *scfg, sstr_t file) { + log_ereport(LOG_VERBOSE, "load_obj_conf"); + + int ret = 0; + + // create object config + pool_handle_t *pool = scfg->pool; + HTTPObjectConfig *conf = pool_calloc(pool, sizeof(HTTPObjectConfig), 1); + if(!conf) { + return NULL; + } + conf->pool = pool; + + // load obj config file + ObjectConfig *cfg = load_object_config(file.ptr); + if(!cfg) { + return NULL; + } + + // convert ObjectConfig to HTTPObjectConfig + + // add objects + conf->nobj = ucx_list_size(cfg->objects); + conf->objects = pool_calloc(pool, conf->nobj, sizeof(httpd_object*)); + if(conf->objects) { + ret = convert_objconf(scfg, cfg, conf, file); + } else { + ret = -1; + } free_object_config(cfg); - return conf; + return !ret ? conf : NULL; } -int mime_conf_reload(ConfigFile *file, ServerConfiguration *cfg) { - MimeConfig *mimecfg = load_mime_config(file->file.ptr); - MimeMap *old_conf = file->data; - - MimeMap *mimemap = malloc(sizeof(MimeMap)); - mimemap->ref = 1; - UcxMap *map = ucx_map_new((mimecfg->ntypes * 3) / 2); - mimemap->map = map; - - // add ext type pairs - UCX_FOREACH(md, mimecfg->directives) { - MimeDirective *d = md->data; - // add the type for each extension to the map - UCX_FOREACH(xl, d->exts) { - sstr_t ext = sstr(xl->data); - sstr_t value = sstrdup(d->type); - ucx_map_sstr_put(map, ext, value.ptr); - } +int mime_conf_load(ServerConfiguration *cfg, sstr_t file) { + MimeConfig *mimecfg = load_mime_config(file.ptr); + if(!mimecfg) { + return -1; } - file->data = mimemap; + int ret = 0; + + // cleanup in case of errors is done by the allocator + MimeMap *mimemap = almalloc(cfg->a, sizeof(MimeMap)); + UcxMap *map = ucx_map_new_a(cfg->a, (mimecfg->ntypes * 3) / 2); - if(old_conf) { - mime_conf_unref(old_conf); + if(mimemap && map) { + mimemap->map = map; + + // add ext type pairs + UCX_FOREACH(md, mimecfg->directives) { + MimeDirective *d = md->data; + // add the type for each extension to the map + UCX_FOREACH(xl, d->exts) { + sstr_t ext = sstr(xl->data); + sstr_t value = sstrdup(d->type); + if(ucx_map_sstr_put(map, ext, value.ptr)) { + ret = -1; + break; + } + } + if(ret) { + break; + } + } + + cfg->mimetypes = mimemap; + } else { + ret = -1; } free_mime_config(mimecfg); - return 0; -} - -void mime_conf_ref(MimeMap *conf) { - if(conf) { - ws_atomic_inc32(&conf->ref); - } + return ret; } -void mime_conf_unref(MimeMap *conf) { - uint32_t ref = ws_atomic_dec32(&conf->ref); - if(ref == 0) { - printf("free MimeConfig %"PRIxPTR"\n", (intptr_t)conf); - UcxMapIterator i = ucx_map_iterator(conf->map); - char *str; - UCX_MAP_FOREACH(key, str, i) { - free(str); - } - ucx_map_free(conf->map); - free(conf); - } -} + -int acl_conf_reload(ConfigFile *file, ServerConfiguration *cfg) { - ACLFile *aclfile = load_acl_file(file->file.ptr); +ACLData* acl_conf_load(ServerConfiguration *cfg, sstr_t file) { + ACLFile *aclfile = load_acl_file(file.ptr); - ACLData *acldata = acl_data_new(); + // TODO: malloc return checks + + ACLData *acldata = acl_data_new(cfg->a); UCX_FOREACH(elm, aclfile->namedACLs) { ACLConfig *ac = elm->data; ACLList *acl = acl_config_convert(cfg, ac); @@ -902,17 +979,13 @@ } free_acl_file(aclfile); - ACLData *old_data = file->data; - file->data = acldata; - if(old_data) { - acl_data_unref(old_data); - } - - return 0; + return acldata; } ACLList* acl_config_convert(ServerConfiguration *cfg, ACLConfig *acl) { - WSAcl *acllist = malloc(sizeof(WSAcl)); + UcxAllocator *a = cfg->a; + + WSAcl *acllist = almalloc(cfg->a, sizeof(WSAcl)); acllist->acl.check = (acl_check_f)wsacl_check; acllist->acl.authdb = NULL; acllist->acl.authprompt = NULL; @@ -925,8 +998,8 @@ } size_t s = ucx_list_size(acl->entries); - WSAce **aces = calloc(s, sizeof(WSAce*)); - WSAce **eces = calloc(s, sizeof(WSAce*)); + WSAce **tmp_aces = calloc(s, sizeof(WSAce*)); + WSAce **tmp_eces = calloc(s, sizeof(WSAce*)); int ai = 0; int ei = 0; @@ -935,36 +1008,36 @@ ACEConfig *acecfg = elm->data; // copy data - WSAce *ace = malloc(sizeof(WSAce)); + WSAce *ace = almalloc(a, sizeof(WSAce)); ace->access_mask = acecfg->access_mask; ace->flags = acecfg->flags; ace->type = acecfg->type; - ace->who = sstrdup(acecfg->who).ptr; + ace->who = sstrdup_a(a, acecfg->who).ptr; // add the entry to the correct array if(ace->type >= ACL_TYPE_AUDIT) { - eces[ei] = ace; + tmp_eces[ei] = ace; ei++; } else { - aces[ai] = ace; + tmp_aces[ai] = ace; ai++; } } // create new entrie arrays with perfect fitting size if(ai > 0) { - acllist->ace = calloc(ai, sizeof(WSAce*)); + acllist->ace = alcalloc(a, ai, sizeof(WSAce*)); } if(ei > 0) { - acllist->ece = calloc(ei, sizeof(WSAce*)); + acllist->ece = alcalloc(a, ei, sizeof(WSAce*)); } - memcpy(acllist->ace, aces, ai*sizeof(WSAce*)); - memcpy(acllist->ece, eces, ei*sizeof(WSAce*)); + memcpy(acllist->ace, tmp_aces, ai*sizeof(WSAce*)); + memcpy(acllist->ece, tmp_eces, ei*sizeof(WSAce*)); acllist->acenum = ai; acllist->ecenum = ei; - free(aces); - free(eces); + free(tmp_aces); + free(tmp_eces); // get authentication information if(acl->authparam) { @@ -975,7 +1048,7 @@ AuthDB *authdb = ucx_map_sstr_get(cfg->authdbs, authdb_str); acllist->acl.authdb = authdb; if(authdb && prompt_str.ptr) { - acllist->acl.authprompt = sstrdup(prompt_str).ptr; + acllist->acl.authprompt = sstrdup_a(a, prompt_str).ptr; } } } @@ -983,69 +1056,47 @@ return &acllist->acl; } -int keyfile_reload(ConfigFile *file, ServerConfiguration *cfg) { - KeyfileConfig *conf = load_keyfile_config(file->file.ptr); - if(!conf) { - return 1; +AuthDB* keyfile_load(ServerConfiguration *cfg, scstr_t file) { + Keyfile *keyfile = keyfile_new(cfg->a); + if(!keyfile) { + return NULL; } - Keyfile *keyfile = keyfile_new(); + KeyfileConfig *conf = load_keyfile_config(file.ptr); + if(!conf) { + return NULL; + } + + AuthDB *ret = &keyfile->authdb; UCX_FOREACH(elm, conf->users) { KeyfileEntry *user = elm->data; - keyfile_add_user( + if(keyfile_add_user( keyfile, user->name, user->hashtype, user->hashdata, user->groups, - user->numgroups); + user->numgroups)) + { + ret = NULL; + break; + } } free_keyfile_config(conf); - Keyfile *old_data = file->data; - file->data = keyfile; - if(old_data) { - keyfile_unref(old_data); - } - - return 0; + return ret; } - -sstr_t cfg_load_file(sstr_t file) { - sstr_t r; - r.ptr = NULL; - r.length = 0; - - if(!file.ptr) { - return r; - } - - sstr_t f = sstrdup(file); - FILE *in = fopen(f.ptr, "r"); - if(!in) { - return r; +pblock* config_obj2pblock(pool_handle_t *pool, ConfigNode *obj) { + pblock *pb = pblock_create_pool(pool, 8); + UCX_FOREACH(elm, obj->children) { + ConfigNode *d = elm->data; + if(d->type == CONFIG_NODE_DIRECTIVE && d->name.length > 0 && ucx_list_size(d->args) == 1) { + ConfigArg *arg = d->args->data; + pblock_nvlinsert(d->name.ptr, d->name.length, arg->value.ptr, arg->value.length, pb); + } } - - UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); - if(!buf) { - fclose(in); - return r; - } - - if(ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write) == 0) { - fclose(in); - ucx_buffer_free(buf); - return r; - } - - r.ptr = buf->space; - r.length = buf->pos; - - free(buf); - fclose(in); - - return r; + return pb; } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/config.h --- a/src/server/daemon/config.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/config.h Sat Sep 24 16:26:10 2022 +0200 @@ -35,14 +35,17 @@ #include "../config/objconf.h" #include "../config/initconf.h" -#include "../config/serverconf.h" #include "../config/mimeconf.h" #include "../config/acl.h" #include "../config/keyfile.h" +#include "../config/serverconfig.h" #include "acldata.h" #include "keyfile_auth.h" #include "log.h" +#include "vfs.h" + +#include "../webdav/webdav.h" #include #include @@ -54,74 +57,81 @@ #endif typedef struct mime_map MimeMap; + +typedef struct WebdavRepository WebdavRepository; +typedef struct WebdavBackendInitData WebdavBackendInitData; -typedef struct _server_configuration { +struct ServerConfiguration { pool_handle_t *pool; + UcxAllocator *a; + UcxMap *host_vs; // map of all vservers. key is the host name UcxList *listeners; // list of all listeners UcxList *logfiles; AccessLog *default_log; UcxMap *authdbs; MimeMap *mimetypes; + UcxMap *resources; + UcxMap *dav; sstr_t tmp; sstr_t user; uint32_t ref; // reference counter -} ServerConfiguration; - - -typedef struct ConfigFile ConfigFile; +}; -typedef int(*cfg_reload_f)(ConfigFile*,ServerConfiguration*); +struct WebdavRepository { + VfsType *vfs; + void *vfsInitData; + UcxList *davBackends; // list of WebdavBackendInitData* + sstr_t object; +}; -struct ConfigFile { - sstr_t file; - time_t last_modified; - cfg_reload_f reload; - void *data; +struct WebdavBackendInitData { + WebdavType *davType; + void *davInitData; }; struct mime_map { UcxMap *map; - uint32_t ref; }; +char* cfg_config_file_path(const char *file); + int load_init_conf(char *file); void init_server_config_parser(); -int cfg_handle_runtime(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_runtime(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_logfile(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_logfile(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_threadpool(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_threadpool(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_eventhandler(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_eventhandler(ServerConfiguration *cfg, ConfigNode *obj); + +int cfg_handle_resourcepool(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_accesslog(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_accesslog(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_authdb(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_authdb(ServerConfiguration *cfg, ConfigNode *obj); + +int cfg_handle_listener(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_listener(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_vs(ServerConfiguration *cfg, ConfigNode *obj); -int cfg_handle_vs(ServerConfiguration *cfg, ServerConfigObject *obj); +int cfg_handle_dav(ServerConfiguration *cfg, ConfigNode *obj); -ServerConfiguration* load_server_conf(ServerConfiguration *old, char *file); +ServerConfiguration* load_server_conf(char *file); void cfg_ref(ServerConfiguration *cfg); void cfg_unref(ServerConfiguration *cfg); +HTTPObjectConfig* objconf_load(ServerConfiguration *scfg, sstr_t file); +int mime_conf_load(ServerConfiguration *cfg, sstr_t file); -int object_conf_reload(ConfigFile *file, ServerConfiguration *cfg); -void object_conf_ref(HTTPObjectConfig *conf); -void object_conf_unref(HTTPObjectConfig *conf); -HTTPObjectConfig* load_obj_conf(char *file); -int mime_conf_reload(ConfigFile *file, ServerConfiguration *cfg); -void mime_conf_ref(MimeMap *conf); -void mime_conf_unref(MimeMap *conf); -int acl_conf_reload(ConfigFile *file, ServerConfiguration *cfg); +ACLData* acl_conf_load(ServerConfiguration *cfg, sstr_t file); ACLList* acl_config_convert(ServerConfiguration *cfg, ACLConfig *acl); -int keyfile_reload(ConfigFile *file, ServerConfiguration *cfg); +AuthDB* keyfile_load(ServerConfiguration *cfg, scstr_t file); -sstr_t cfg_load_file(sstr_t file); +pblock* config_obj2pblock(pool_handle_t *pool, ConfigNode *obj); #ifdef __cplusplus } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/configmanager.c --- a/src/server/daemon/configmanager.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/configmanager.c Sat Sep 24 16:26:10 2022 +0200 @@ -38,111 +38,39 @@ #include "configmanager.h" static ServerConfiguration *current_config = NULL; -static time_t sc_last_modified = 0; -static UcxMap *config_files; static conf_global_vars_s global_vars; void init_configuration_manager() { /* init parser */ init_server_config_parser(); - - config_files = ucx_map_new(16); } NSAPI_PUBLIC conf_global_vars_s* conf_getglobals() { return &global_vars; } -void cfgmgr_attach_file(ConfigFile *cf) { - ucx_map_sstr_put(config_files, cf->file, cf); -} - -ConfigFile* cfgmgr_get_file(sstr_t name) { - return ucx_map_sstr_get(config_files, name); -} - -int cfgmgr_reload_file(ConfigFile *f, ServerConfiguration *conf, int *reload) { - struct stat s; - if(stat(f->file.ptr, &s) != 0) { - fprintf( - stderr, - "Error: Cannot get stat of file %s\n", f->file.ptr); - perror("cfgmgr_load_config: stat"); - return -1; - } +int cfgmgr_load_config(ServerConfiguration **set_cfg) { + ServerConfiguration *config = load_server_conf("config/server.conf"); - //printf("1 time: %d - %d\n", f->last_modified, s.st_mtim.tv_sec); - if(f->last_modified != s.st_mtime) { - /* reload the file */ - if(f->last_modified != 0) { - log_ereport( - LOG_INFORM, - "reload configuration file: %s", - f->file.ptr); - } - if(f->reload(f, conf)) { - return -1; - } - f->last_modified = s.st_mtime; - if(reload) { - *reload = 1; - } - } - return 0; -} - -int cfgmgr_load_config(ServerConfiguration **set_cfg) { - int cfgreload = 0; - - /* check config files */ - UcxMapIterator iter = ucx_map_iterator(config_files); - ConfigFile *f; - UCX_MAP_FOREACH(key, f, iter) { - if(cfgmgr_reload_file(f, current_config, &cfgreload) == -1) { - return -1; - } - } - - struct stat s; - if(stat("config/server.conf", &s) != 0) { - perror("stat(\"config/server.conf\")"); + if(!config) { return -1; } - - ServerConfiguration *config; - if(cfgreload || !current_config || sc_last_modified != s.st_mtime) { - config = load_server_conf( - current_config, - "config/server.conf"); - - if(config == NULL) { - return -1; - } - - sc_last_modified = s.st_mtime; - } else { - log_ereport(LOG_VERBOSE, "no reconfig required"); - config = current_config; - } if(set_cfg) { *set_cfg = config; } - ServerConfiguration *old_conf = NULL; - if(current_config != config) { - old_conf = current_config; + + if(current_config) { + cfg_unref(current_config); } current_config = config; - if(old_conf) { - cfg_unref(old_conf); - } + return 0; } ServerConfiguration *cfgmgr_get_server_config() { - //cfg_ref(current_config); return current_config; } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/configmanager.h --- a/src/server/daemon/configmanager.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/configmanager.h Sat Sep 24 16:26:10 2022 +0200 @@ -46,9 +46,6 @@ void init_configuration_manager(); -void cfgmgr_attach_file(ConfigFile *cf); -ConfigFile* cfgmgr_get_file(sstr_t name); -int cfgmgr_reload_file(ConfigFile *f, ServerConfiguration *conf, int *reload); int cfgmgr_load_config(ServerConfiguration **cfg); ServerConfiguration* cfgmgr_get_server_config(); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/event.c --- a/src/server/daemon/event.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/event.c Sat Sep 24 16:26:10 2022 +0200 @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "../../ucx/map.h" +#include #include "../util/atomic.h" #include "event.h" @@ -88,7 +88,7 @@ } EventHandlerConfig cfg; - cfg.name = sstr("default"); + cfg.name = SC("default"); cfg.nthreads = 1; cfg.isdefault = 1; diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/event.h --- a/src/server/daemon/event.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/event.h Sat Sep 24 16:26:10 2022 +0200 @@ -43,7 +43,7 @@ } EVHandler; typedef struct event_handler_conf { - sstr_t name; + scstr_t name; int nthreads; int isdefault; } EventHandlerConfig; diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/event_bsd.c --- a/src/server/daemon/event_bsd.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/event_bsd.c Sat Sep 24 16:26:10 2022 +0200 @@ -67,47 +67,75 @@ timeout.tv_sec = 600; struct kevent events[64]; - struct kevent changes[64]; + struct kevent changes[128]; int numchanges = 0; for(;;) { // wait for events - int nev = kevent(ev->kqueue, changes, numchanges, events, 64, &timeout); + int nev = kevent(ev->kqueue, changes, numchanges, events, 64, &timeout); if(nev == -1) { - // TODO: check for error - perror("kevent"); + log_ereport(LOG_FAILURE, "kevent: %s", strerror(errno)); continue; } numchanges = 0; for(int i=0;ievents; + if(event->fn) { - int ep = event->events; - if(event->fn(ev, event)) { - if(event->events != ep) { - changes[numchanges++].filter = ev_convert2sys_events(ep); + int saved_ev = event->events; + if(!event->fn(ev, event)) { + // ret 0 => remove event + + if(event->finish) { + event->finish(ev, event); } - } else if(event->finish) { - changes[numchanges++].filter = ev_convert2sys_events(ep); - event->finish(ev, event); + + event_events = 0; + } else { + event_events = event->events; + } + + // if events have changed, we need to add/remove filters + if(saved_ev != event_events) { + int e = event_events; + int e_fd = events[i].ident; + if((e & EVENT_POLLIN) != (saved_ev & EVENT_POLLIN)) { + if((e & EVENT_POLLIN) == EVENT_POLLIN) { + // add + EV_SET(&changes[numchanges++], e_fd, EVFILT_READ, EV_ADD, 0, 0, event); + } else { + // delete + EV_SET(&changes[numchanges++], e_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + } + } + if((e & EVENT_POLLOUT) != (saved_ev & EVENT_POLLOUT)) { + if((e & EVENT_POLLOUT) == EVENT_POLLOUT) { + // add + EV_SET(&changes[numchanges++], e_fd, EVFILT_WRITE, EV_ADD, 0, 0, event); + } else { + // delete + EV_SET(&changes[numchanges++], e_fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + } + } } } } } } -int ev_convert2sys_events(int events) { - int e = 0; - if((events & EVENT_POLLIN) == EVENT_POLLIN) { - e |= EVFILT_READ; - } - if((events & EVENT_POLLOUT) == EVENT_POLLOUT) { - e |= EVFILT_WRITE; - } - return e; -} - int ev_pollin(EventHandler *h, int fd, Event *event) { event->events = EVENT_POLLIN; struct kevent kev; @@ -122,6 +150,35 @@ return kevent(h->kqueue, &kev, 1, NULL, 0, NULL); } +int ev_remove_poll(EventHandler *h, int fd) { + struct kevent kev; + EV_SET(&kev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + int r1 = kevent(h->kqueue, &kev, 1, NULL, 0, NULL); + EV_SET(&kev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + int r2 = kevent(h->kqueue, &kev, 1, NULL, 0, NULL); + // in caase r1 or r2 was successful, we return 0 (no error) + return r1 != -1 || r2 != -1 ? 0 : 1; +} + int event_send(EventHandler *h, Event *event) { return 0; } + +// TODO: remove this fake aio +int ev_aioread(int fd, aiocb_s *cb) { + ssize_t result = pread(fd, cb->buf, cb->nbytes, cb->offset); + cb->result = result; + if(result < 0) { + cb->result_errno = errno; + } + return event_send(cb->evhandler, cb->event); +} + +int ev_aiowrite(int fd, aiocb_s *cb) { + ssize_t result = pwrite(fd, cb->buf, cb->nbytes, cb->offset); + cb->result = result; + if(result < 0) { + cb->result_errno = errno; + } + return event_send(cb->evhandler, cb->event); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/http.c --- a/src/server/daemon/http.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/http.c Sat Sep 24 16:26:10 2022 +0200 @@ -198,7 +198,7 @@ /* ---------------------------- http_set_finfo ---------------------------- */ -static inline int set_finfo(Session *sn, Request *rq, off_t size, time_t mtime) +static inline int set_finfo(Session *sn, Request *rq, off_t size, time_t mtime, const char *etag) { struct tm mtms; struct tm *mtm = system_gmtime(&mtime, &mtms); @@ -223,15 +223,19 @@ snprintf(pp->value, content_length_size, "%lld", (long long)size); pblock_kpinsert(pb_key_content_length, pp, rq->srvhdrs); - char *etag; if (http_etag) { /* Insert Etag */ - pp = pblock_key_param_create(rq->srvhdrs, pb_key_etag, NULL, MAX_ETAG); - if (!pp || !pp->value) - return REQ_ABORTED; - http_format_etag(sn, rq, pp->value, MAX_ETAG, size, mtime); - pblock_kpinsert(pb_key_etag, pp, rq->srvhdrs); - etag = pp->value; + if(etag) { + pblock_kvinsert(pb_key_etag, etag, strlen(etag), rq->srvhdrs); + } else { + pp = pblock_key_param_create(rq->srvhdrs, pb_key_etag, NULL, MAX_ETAG); + if (!pp || !pp->value) + return REQ_ABORTED; + http_format_etag(sn, rq, pp->value, MAX_ETAG, size, mtime); + pblock_kpinsert(pb_key_etag, pp, rq->srvhdrs); + etag = pp->value; + } + } else { etag = NULL; } @@ -242,7 +246,11 @@ NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo) { - return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime); + return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime, NULL); +} + +NSAPI_PUBLIC int http_set_finfo_etag(Session *sn, Request *rq, struct stat *finfo, const char *etag) { + return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime, etag); } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/httplistener.c --- a/src/server/daemon/httplistener.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/httplistener.c Sat Sep 24 16:26:10 2022 +0200 @@ -237,23 +237,18 @@ } // TODO: cleanup on error - sstr_t file; int ret; char errbuf[512]; if(!conf->chainfile.ptr) { - file = sstrdup(conf->certfile); - ret = SSL_CTX_use_certificate_file(ctx, file.ptr, SSL_FILETYPE_PEM); - free(file.ptr); + ret = SSL_CTX_use_certificate_file(ctx, conf->certfile.ptr, SSL_FILETYPE_PEM); if(!ret) { ERR_error_string(ERR_get_error(), errbuf); log_ereport(LOG_MISCONFIG, "Cannot load ssl chain file: %s", errbuf); return NULL; } } else { - file = sstrdup(conf->chainfile); - ret = SSL_CTX_use_certificate_chain_file(ctx, file.ptr); - free(file.ptr); + ret = SSL_CTX_use_certificate_chain_file(ctx, conf->chainfile.ptr); if(!ret) { ERR_error_string(ERR_get_error(), errbuf); log_ereport(LOG_MISCONFIG, "Cannot load ssl cert file: %s", errbuf); @@ -261,9 +256,7 @@ } } - file = sstrdup(conf->privkeyfile); - ret = SSL_CTX_use_PrivateKey_file(ctx, file.ptr, SSL_FILETYPE_PEM); - free(file.ptr); + ret = SSL_CTX_use_PrivateKey_file(ctx, conf->privkeyfile.ptr, SSL_FILETYPE_PEM); if(!ret) { ERR_error_string(ERR_get_error(), errbuf); log_ereport(LOG_MISCONFIG, "Cannot load ssl key file: %s", errbuf); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/httplistener.h --- a/src/server/daemon/httplistener.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/httplistener.h Sat Sep 24 16:26:10 2022 +0200 @@ -64,10 +64,10 @@ int nacceptors; WSBool blockingio; WSBool ssl; - sstr_t certfile; - sstr_t privkeyfile; - sstr_t chainfile; - sstr_t disable_proto; + scstr_t certfile; + scstr_t privkeyfile; + scstr_t chainfile; + scstr_t disable_proto; }; struct _acceptor { diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/httpparser.c --- a/src/server/daemon/httpparser.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/httpparser.c Sat Sep 24 16:26:10 2022 +0200 @@ -187,7 +187,6 @@ for(;irequest->method.length = i; } else if(ns) { if(line.ptr[i] != ' ') { @@ -200,9 +199,8 @@ ns = 0; int s = i; for(;irequest->uri.length = i - s; } else if(ns) { if(line.ptr[i] > 32) { @@ -215,9 +213,8 @@ ns = 0; s = i; for(;irequest->httpv.length = i - s; } else if(ns) { if(line.ptr[i] > 32) { diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/httprequest.c --- a/src/server/daemon/httprequest.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/httprequest.c Sat Sep 24 16:26:10 2022 +0200 @@ -87,6 +87,48 @@ return S("/"); } +NSAPISession* nsapisession_create(pool_handle_t *pool) { + NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); + if(!sn) { + return NULL; + } + + ZERO(sn, sizeof(NSAPISession)); + + sn->sn.pool = pool; + sn->allocator = util_pool_allocator(pool); + + sn->sn.client = pblock_create_pool(sn->sn.pool, 8); + if(!sn->sn.client) { + pool_free(pool, sn); + return NULL; + } + sn->sn.fill = 1; + + return sn; +} + +int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io) { + SessionHandler *sh = conn->session_handler; + WSBool ssl; + IOStream *sio = sh->create_iostream(sh, conn, sn->sn.pool, &ssl); + if(!sio) { + return 1; + } + *io = sio; + IOStream *http = httpstream_new(sn->sn.pool, sio); + if(!http) { + return 1; + } + sn->connection = conn; + sn->netbuf = inbuf; + sn->sn.csd = http; + sn->sn.ssl = ssl; + sn->sn.inbuf = inbuf; + sn->sn.inbuf->sd = http; + return 0; +} + int handle_request(HTTPRequest *request, threadpool_t *thrpool, EventHandler *ev) { // handle nsapi request @@ -94,11 +136,11 @@ pool_handle_t *pool = pool_create(); // create nsapi data structures - NSAPISession *sn = pool_malloc(pool, sizeof(NSAPISession)); + NSAPISession *sn = nsapisession_create(pool); if(sn == NULL) { /* TODO: error */ } - ZERO(sn, sizeof(NSAPISession)); + NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); if(rq == NULL) { /* TODO: error */ @@ -108,25 +150,16 @@ rq->phase = NSAPIAuthTrans; // fill session structure - sn->connection = request->connection; - sn->netbuf = request->netbuf; - sn->sn.pool = pool; - SessionHandler *sh = request->connection->session_handler; - WSBool ssl; - IOStream *io = sh->create_iostream(sh, request->connection, pool, &ssl); - sn->sn.csd = httpstream_new(pool, io); - sn->sn.ssl = ssl; - - sn->sn.client = pblock_create_pool(sn->sn.pool, 8); - sn->sn.next = NULL; - sn->sn.fill = 1; - sn->sn.subject = NULL; + IOStream *io = NULL; + if(nsapisession_setconnection(sn, request->connection, request->netbuf, &io)) { + // TODO: error + } if(!ev) { ev = ev_instance(get_default_event_handler()); } sn->sn.ev = ev; - + // the session needs the current server configuration sn->config = request->connection->listener->cfg; @@ -198,13 +231,13 @@ /* * get absolute path and query of the request uri */ - // TODO: check for '#' + // TODO: check for '#' #72 sstr_t absPath = http_request_get_abspath(request); if(!absPath.ptr) { // TODO: error msg return 1; } else if(absPath.ptr[0] == '*') { - // TODO: implement global OPTIONS + // TODO: implement global OPTIONS #71 return 1; } @@ -342,44 +375,42 @@ // check for request body and prepare input buffer char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers); if(ctlen_str) { - int ctlen = atoi(ctlen_str); - - //printf("request body length: %d\n", ctlen); + int64_t ctlen; + if(util_strtoint(ctlen_str, &ctlen)) { + netbuf *nb = sn->netbuf; + HttpStream *net_io = (HttpStream*)sn->sn.csd; + net_io->read_eof = WS_FALSE; - netbuf *nb = request->netbuf; + // how many bytes are already read and in the buffer + int cur_input_available = nb->cursize - nb->pos; - // create new netbuf - HttpStream *net_io = (HttpStream*)httpstream_new(pool, io); - net_io->max_read = ctlen; - - sn->sn.inbuf = pool_malloc(pool, sizeof(netbuf)); - sn->sn.inbuf->sd = net_io; - sn->sn.inbuf->pos = 0; - - // prepare buffer - int cur_input_len = nb->cursize - nb->pos; - - if(cur_input_len >= ctlen) { - /* - * all data is already in the primary input buffer - * just link the new netbuf to the primary buffer - */ - sn->sn.inbuf->maxsize = ctlen; - sn->sn.inbuf->cursize = ctlen; - sn->sn.inbuf->inbuf = nb->inbuf + nb->pos; - } else { - sn->sn.inbuf->maxsize = (ctlen > 2048) ? (2048) : (ctlen); - sn->sn.inbuf->inbuf = pool_malloc(pool, sn->sn.inbuf->maxsize); - - if(cur_input_len > 0) { - // we have read a part of the request body -> copy to netbuf - memcpy(sn->sn.inbuf->inbuf, nb->inbuf+nb->pos, cur_input_len); + if(cur_input_available >= ctlen) { + // we have the whole request body in the buffer and + // maybe even more + // no more read from the socket is necessary to get the body, + // therefore disable it + net_io->max_read = 0; + } else { + // read still required to get the complete request body + net_io->max_read = ctlen - cur_input_available; } - - sn->sn.inbuf->cursize = cur_input_len; + //printf("request body length: %d\n", ctlen); + } // else: should we abort? + } + char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers); + if(transfer_encoding) { + if(!strcmp(transfer_encoding, "chunked")) { + netbuf *nb = sn->netbuf; + sn->buffer = (char*)nb->inbuf; + sn->pos = nb->pos; + sn->cursize = nb->cursize; + + if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) { + pool_destroy(pool); + // TODO: error 500 + return 1; + } } - } else { - sn->sn.inbuf = NULL; } // @@ -535,6 +566,33 @@ } int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq) { + request_free_resources(sn, rq); + + WSBool read_stream_eof = httpstream_eof(sn->sn.csd); + if(!read_stream_eof) { + log_ereport(LOG_WARN, "request input stream not closed"); + // TODO: clean stream + rq->rq.rq_attr.keep_alive = 0; // workaround + } + if(sn->pos < sn->cursize) { + log_ereport(LOG_WARN, "nsapi_finish_request: TODO: remaining bytes in buffer"); + // TODO: reuse buffer in next request + rq->rq.rq_attr.keep_alive = 0; // workaround + } + + if(rq->rq.senthdrs) { + // flush buffer and add termination if chunked encoding + // is enabled + net_finish(sn->sn.csd); + } else { + // why was no response sent? + // something must have gone wrong + // terminate the session + char *clf_req = pblock_findkeyval(pb_key_clf_request, rq->rq.reqpb); + log_ereport(LOG_WARN, "nsapi_finish_request: no response header: request: %s", clf_req); + rq->rq.rq_attr.keep_alive = 0; + } + if(rq->rq.rq_attr.keep_alive) { SessionHandler *sh = sn->connection->session_handler; sh->keep_alive(sh, sn->connection); @@ -557,6 +615,16 @@ return 0; } +void request_free_resources(NSAPISession *sn, NSAPIRequest *rq) { + if(!rq->resources) return; + + UcxMapIterator i = ucx_map_iterator(rq->resources); + ResourceData *resource; + UCX_MAP_FOREACH(key, resource, i) { + resourcepool_free(&sn->sn, &rq->rq, resource); + } +} + int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq) { HTTPObjectConfig *objconf = rq->vs->objects; httpd_object *obj = objconf->objects[0]; @@ -835,12 +903,12 @@ } if(ret != REQ_NOACTION) { - if(ret == REQ_PROCEED) { - /* - * flush buffer and add termination if chunked encoding - * is enabled - */ - net_finish(sn->sn.csd); + if(ret == REQ_PROCEED && !rq->rq.senthdrs) { + // a service SAF must send a response + // senthdrs == 0 indicators something has gone + // wrong + protocol_status(&sn->sn, &rq->rq, 500, NULL); + ret = REQ_ABORTED; } else if(ret == REQ_PROCESSING) { // save nsapi context rq->context.objset_index = i; @@ -875,11 +943,18 @@ if(ret == REQ_NOACTION) { directive *d = dt->dirs[j]; - // check status code parameter + // check status code parameter + // Error SAFs can specify, for which status code they should + // be executed char *status = pblock_findkeyval(pb_key_type, d->param); if(status) { - int statuscode = atoi(status); - if(statuscode != rq->rq.status_num) { + int64_t statuscode = -1; + if(!util_strtoint(status, &statuscode)) { + log_ereport( + LOG_WARN, + "nsapi_error: directive '%s' ignored: invalid type parameter: integer status code expected", + d->func->name); + } else if(statuscode != rq->rq.status_num) { continue; } } @@ -895,10 +970,8 @@ } if(ret != REQ_NOACTION) { if(ret == REQ_PROCEED) { - /* - * flush buffer and add termination if chunked encoding - * is enabled - */ + // flush buffer and add termination if chunked encoding + // is enabled net_finish(sn->sn.csd); } else if(ret == REQ_PROCESSING) { // save nsapi context @@ -914,7 +987,9 @@ if(ret != REQ_PROCEED) { // default error handler - nsapi_error_request((Session*)sn, (Request*)rq); + if(!rq->rq.senthdrs) { + nsapi_error_request((Session*)sn, (Request*)rq); + } } return ret; diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/httprequest.h --- a/src/server/daemon/httprequest.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/httprequest.h Sat Sep 24 16:26:10 2022 +0200 @@ -73,6 +73,10 @@ sstr_t http_request_get_abspath(HTTPRequest *req); + +NSAPISession* nsapisession_create(pool_handle_t *pool); +int nsapisession_setconnection(NSAPISession *sn, Connection *conn, netbuf *inbuf, IOStream **io); + /* * starts request processing after reading the request header * @@ -89,6 +93,8 @@ int nsapi_handle_request(NSAPISession *sn, NSAPIRequest *rq); int nsapi_finish_request(NSAPISession *sn, NSAPIRequest *rq); +void request_free_resources(NSAPISession *sn, NSAPIRequest *rq); + int nsapi_authtrans(NSAPISession *sn, NSAPIRequest *rq); int nsapi_nametrans(NSAPISession *sn, NSAPIRequest *rq); int nsapi_pathcheck(NSAPISession *sn, NSAPIRequest *rq); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/keyfile_auth.c --- a/src/server/daemon/keyfile_auth.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/keyfile_auth.c Sat Sep 24 16:26:10 2022 +0200 @@ -43,40 +43,18 @@ #include "keyfile_auth.h" -Keyfile* keyfile_new() { - Keyfile *keyfile = malloc(sizeof(Keyfile)); +Keyfile* keyfile_new(UcxAllocator *a) { + Keyfile *keyfile = alcalloc(a, 1, sizeof(Keyfile)); + if(!keyfile) { + return NULL; + } keyfile->authdb.get_user = keyfile_get_user; keyfile->authdb.use_cache = 0; - keyfile->users = ucx_map_new(16); - keyfile->ref = 1; + keyfile->users = ucx_map_new_a(a, 16); return keyfile; } -void keyfile_ref(Keyfile *keyfile) { - ws_atomic_inc32(&keyfile->ref); -} - -void keyfile_unref(Keyfile *keyfile) { - uint32_t ref = ws_atomic_dec32(&keyfile->ref); - if(ref == 0) { - UcxMapIterator i = ucx_map_iterator(keyfile->users); - KeyfileUser *user; - UCX_MAP_FOREACH(key, user, i) { - free(user->user.name); - free(user->hash); - for(int n=0;nnumgroups;n++) { - free(user->groups[n].ptr); - } - free(user->groups); - } - ucx_map_free(keyfile->users); - - free(keyfile->authdb.name); - free(keyfile); - } -} - -void keyfile_add_user( +int keyfile_add_user( Keyfile *keyfile, sstr_t name, enum KeyfileHashType hash_type, @@ -84,14 +62,16 @@ sstr_t *groups, size_t ngroups) { + UcxAllocator *a = keyfile->users->allocator; + if(hash.length < 12) { // hash too short // TODO: log - return; + return -1; } - KeyfileUser *user = malloc(sizeof(KeyfileUser)); - user->user.name = sstrdup(name).ptr; + KeyfileUser *user = almalloc(a, sizeof(KeyfileUser)); + user->user.name = sstrdup_a(a, name).ptr; user->user.uid = -1; user->user.gid = -1; user->user.verify_password = keyfile_user_verify_password; @@ -99,16 +79,29 @@ user->user.free = keyfile_user_free; user->hash_type = hash_type; - user->hash = malloc(hash.length + 1); + user->hash = almalloc(a, hash.length + 1); + + if(!user->user.name || !user->hash) { + return -1; + } + user->hashlen = util_base64decode(hash.ptr, hash.length, user->hash); - user->groups = calloc(ngroups, sizeof(sstr_t)); - for(int i=0;igroups[i] = sstrdup(groups[i]); + if(ngroups > 0) { + user->groups = alcalloc(a, ngroups, sizeof(sstr_t)); + if(!user->groups) { + return -1; + } + for(int i=0;igroups[i] = sstrdup_a(a, groups[i]); + } + + } else { + user->groups = NULL; } // add to keyfile - ucx_map_sstr_put(keyfile->users, name, user); + return ucx_map_sstr_put(keyfile->users, name, user); } // authdb functions diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/keyfile_auth.h --- a/src/server/daemon/keyfile_auth.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/keyfile_auth.h Sat Sep 24 16:26:10 2022 +0200 @@ -48,9 +48,8 @@ }; struct keyfile { - AuthDB authdb; - UcxMap *users; - uint32_t ref; + AuthDB authdb; + UcxMap *users; }; struct keyfile_user { @@ -62,11 +61,9 @@ size_t hashlen; }; -Keyfile* keyfile_new(); -void keyfile_ref(Keyfile *keyfile); -void keyfile_unref(Keyfile *keyfile); +Keyfile* keyfile_new(UcxAllocator *a); -void keyfile_add_user( +int keyfile_add_user( Keyfile *keyfile, sstr_t user, enum KeyfileHashType hash_type, diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/ldap_auth.c --- a/src/server/daemon/ldap_auth.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/ldap_auth.c Sat Sep 24 16:26:10 2022 +0200 @@ -47,9 +47,9 @@ #endif } -AuthDB* create_ldap_authdb(char *name, LDAPConfig *conf) { - LDAPAuthDB *authdb = malloc(sizeof(LDAPAuthDB)); - authdb->authdb.name = strdup(name); +AuthDB* create_ldap_authdb(ServerConfiguration *cfg, const char *name, LDAPConfig *conf) { + LDAPAuthDB *authdb = almalloc(cfg->a, sizeof(LDAPAuthDB)); + authdb->authdb.name = pool_strdup(cfg->pool, name); authdb->authdb.get_user = ldap_get_user; authdb->authdb.use_cache = 1; authdb->config = *conf; @@ -64,7 +64,7 @@ // initialize group cache authdb->groups.first = NULL; authdb->groups.last = NULL; - authdb->groups.map = ucx_map_new(32); + authdb->groups.map = ucx_map_new_a(cfg->a, 32); return (AuthDB*) authdb; } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/ldap_auth.h --- a/src/server/daemon/ldap_auth.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/ldap_auth.h Sat Sep 24 16:26:10 2022 +0200 @@ -34,6 +34,8 @@ #include #include +#include "config.h" + #ifdef __cplusplus extern "C" { #endif @@ -90,7 +92,7 @@ LDAPGroup *next; }; -AuthDB* create_ldap_authdb(char *name, LDAPConfig *conf); +AuthDB* create_ldap_authdb(ServerConfiguration *cfg, const char *name, LDAPConfig *conf); LDAP* get_ldap_session(LDAPAuthDB *authdb); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/log.c --- a/src/server/daemon/log.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/log.c Sat Sep 24 16:26:10 2022 +0200 @@ -335,7 +335,9 @@ * This source file only manages access log files. IO is performed directly * by AddLog safs. */ -LogFile* get_access_log_file(sstr_t file) { +LogFile* get_access_log_file(scstr_t file) { + // TODO: this looks dubious + if(!access_log_files) { access_log_files = ucx_map_new(4); } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/log.h --- a/src/server/daemon/log.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/log.h Sat Sep 24 16:26:10 2022 +0200 @@ -40,10 +40,10 @@ #endif typedef struct { - char *file; - char *level; - int log_stdout; - int log_stderr; + const char *file; + const char *level; + int log_stdout; + int log_stderr; } LogConfig; typedef struct { @@ -75,7 +75,7 @@ void log_remove_logdup(LogDup *dup); // access logging -LogFile* get_access_log_file(sstr_t file); +LogFile* get_access_log_file(scstr_t file); #ifdef __cplusplus } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/main.c --- a/src/server/daemon/main.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/main.c Sat Sep 24 16:26:10 2022 +0200 @@ -39,7 +39,7 @@ #include "../util/plist.h" #include "../util/date.h" -#include "../../ucx/string.h" +#include #include "webserver.h" #include "log.h" @@ -167,7 +167,7 @@ int status; status = webserver_init(); if(status != 0) { - log_ereport(LOG_FAILURE, "cannot initialize server."); + log_ereport(LOG_FAILURE, "Cannot initialize server."); return EXIT_FAILURE; } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/objs.mk --- a/src/server/daemon/objs.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/objs.mk Sat Sep 24 16:26:10 2022 +0200 @@ -56,6 +56,7 @@ DAEMONOBJ += acl.o DAEMONOBJ += acldata.o DAEMONOBJ += vfs.o +DAEMONOBJ += resourcepool.o # add additional platform dependend objects # defined in generated config.mk diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/protocol.c --- a/src/server/daemon/protocol.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/protocol.c Sat Sep 24 16:26:10 2022 +0200 @@ -347,7 +347,7 @@ } // set stream property - HttpStream *stream = (HttpStream*)sn->csd; + HttpStream *stream = (HttpStream*)sn->csd; // TODO: make this typesafe stream->chunked_enc = 1; rq->rq_attr.chunked = 1; } @@ -377,6 +377,16 @@ return 0; } +int http_send_continue(Session *sn) { + NSAPISession *s = (NSAPISession*)sn; + sstr_t msg = S("HTTP/1.1 100 Continue\r\n\r\n"); + int w = s->connection->write(s->connection, msg.ptr, msg.length); + if(w != msg.length) { + return 1; + } + return 0; +} + int request_header(char *name, char **value, Session *sn, Request *rq) { const pb_key *key = pblock_key(name); pb_param *pp = pblock_findkey(key, rq->headers); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/protocol.h --- a/src/server/daemon/protocol.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/protocol.h Sat Sep 24 16:26:10 2022 +0200 @@ -46,6 +46,8 @@ int http_start_response(Session *sn, Request *rq); +int http_send_continue(Session *sn); + int request_header(char *name, char **value, Session *sn, Request *rq); void http_get_scheme_host_port( diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/request.h --- a/src/server/daemon/request.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/request.h Sat Sep 24 16:26:10 2022 +0200 @@ -32,6 +32,8 @@ #include "../public/nsapi.h" #include "../util/object.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -46,6 +48,7 @@ uint16_t port; NSAPIContext context; void *jvm_context; + UcxMap *resources; }; /* macros for context access */ diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/resourcepool.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/daemon/resourcepool.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,256 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "resourcepool.h" +#include "request.h" +#include "session.h" +#include "../public/nsapi.h" +#include "../util/atomic.h" + +#define RESOURCE_POOL_MAX_DEFAULT 32 + +#define RESOURCE_POOL_MAX_ALLOC 268435455 + +static UcxMap *resource_pool_types; + +int init_resource_pools(void) { + resource_pool_types = ucx_map_new(4); + return resource_pool_types ? 0 : 1; +} + +int resourcepool_register_type(const char *type_name, ResourceType *type_info) { + if(ucx_map_cstr_put(resource_pool_types, type_name, type_info)) { + log_ereport(LOG_CATASTROPHE, "resourcepool_register_type: OOM"); + return 1; + } + return 0; +} + + + +int resourcepool_new(ServerConfiguration *cfg, scstr_t type, scstr_t name, ConfigNode *node) { + ResourceType *restype = ucx_map_sstr_get(resource_pool_types, type); + if(!restype) { + log_ereport(LOG_MISCONFIG, "Unknown resource pool type: %s", type.ptr); + return 1; + } + + // convert ConfigNode to pblock + // no sub-objects allowed for this specific ConfigNode, therefore + // it can be represented as key-value-pairs + pblock *param = config_obj2pblock(cfg->pool, node); + if(!param) { + log_ereport(LOG_FAILURE, "resourcepool_new: OOM"); + return 1; + } + + ResourcePool *respool = pool_malloc(cfg->pool, sizeof(ResourcePool)); + if(!respool) { + log_ereport(LOG_FAILURE, "resourcepool_new: OOM"); + return 1; + } + respool->pool = cfg->pool; + + void *respool_data = restype->init(cfg->pool, name.ptr, param); + if(!respool_data) { + log_ereport(LOG_FAILURE, "Cannot create resource pool data: pool: %s type: %s", name.ptr, type.ptr); + return 1; + } + + respool->type = restype; + respool->data = respool_data; + respool->min = 4; // TODO: get from node + respool->max = RESOURCE_POOL_MAX_DEFAULT; // TODO: get from node + + respool->numcreated = 0; + respool->numresources = 0; + + + // don't allow too large resource pools + // this prevents the need to check malloc integer overflows + if(respool->max > RESOURCE_POOL_MAX_ALLOC) { + respool->max = RESOURCE_POOL_MAX_ALLOC; + log_ereport(LOG_WARN, "Resource pool %s: limit max to %d", name.ptr, respool->max); + } + + respool->resalloc = respool->max; + respool->resources = pool_malloc(cfg->pool, respool->resalloc * sizeof(ResourceDataPrivate*)); + + if(!respool->resources || ucx_map_sstr_put(cfg->resources, name, respool)) { + log_ereport(LOG_FAILURE, "Cannot add resource pool: OOM"); + // the only cleanup we have to do + restype->destroy(respool_data); + return 1; + } + + pthread_mutex_init(&respool->lock, NULL); + pthread_cond_init(&respool->available, NULL); + + return 0; +} + +static ResourceData* s_resourcepool_lookup(ServerConfiguration *cfg, Request *opt_rq, Session *opt_sn, const char *name, int flags) { + NSAPIRequest *request = (NSAPIRequest*)opt_rq; + NSAPISession *session = (NSAPISession*)opt_sn; + ResourceDataPrivate *resource = NULL; + + // was this resource already used by this request? + if(request && request->resources) { + resource = ucx_map_cstr_get(request->resources, name); + if(resource) { + return &resource->data; + } + } + + ResourcePool *respool = ucx_map_cstr_get(cfg->resources, name); + if(!respool) return NULL; + + + pthread_mutex_lock(&respool->lock); + WSBool createResource = FALSE; + if(respool->numcreated < respool->min) { + createResource = TRUE; + } + + if(createResource) { + // create a new resource and store it in the resourcepool + void *resourceData = respool->type->createresource(respool->data); + if(resourceData) { + respool->numcreated++; + + resource = pool_malloc(respool->pool, sizeof(ResourceDataPrivate)); + if(resource) { + resource->data.data = respool->type->getresourcedata(resourceData); + resource->data.resourcepool = respool; + resource->resdata = resourceData; + } else { + respool->type->freeresource(respool->data, resourceData); + log_ereport(LOG_CATASTROPHE, "resourcepool_lookup: OOM"); + } + } + // else: respool->type->createresource does logging in case of errors + } else if(respool->numresources > 0) { + resource = respool->resources[--respool->numresources]; + } else { + // wait for free resource + pthread_cond_wait(&respool->available, &respool->lock); + if(respool->numresources > 0) { + resource = respool->resources[--respool->numresources]; + } + } + + // save the resource in the request object, for caching and also + // for cleanup later + int err = 0; + if(resource) { + if(request && session) { + if(!request->resources) { + request->resources = ucx_map_new_a(&session->allocator, 8); + } + + if(request->resources) { + if(ucx_map_cstr_put(request->resources, name, resource)) { + err = 1; + } + } else { + err = 1; + } + } // else: lookup is outside of any request context + + if(respool->type->prepare(respool->data, resource->resdata)) { + err = -1; + } + } + + if(err) { + // err == 1 caused by OOM + log_ereport(LOG_FAILURE, "resourcepool_lookup: OOM"); + // cleanup + resourcepool_destroy_resource(resource); + resource = NULL; + } + + pthread_mutex_unlock(&respool->lock); + + return (ResourceData*)resource; +} + +ResourceData* resourcepool_cfg_lookup(ServerConfiguration *cfg, const char *name, int flags) { + return s_resourcepool_lookup(cfg, NULL, NULL, name, flags); +} + +ResourceData* resourcepool_lookup(Session *sn, Request *rq, const char *name, int flags) { + NSAPISession *session = (NSAPISession*)sn; + ServerConfiguration *cfg = session->config; + return s_resourcepool_lookup(cfg, rq, sn, name, flags); +} + +void resourcepool_free(Session *sn, Request *rq, ResourceData *resource) { + ResourceDataPrivate *res = (ResourceDataPrivate*)resource; + ResourcePool *respool = resource->resourcepool; + + if(respool->type->finish(respool->data, res->resdata)) { + log_ereport(LOG_FAILURE, "resourcepool_free: finish failed"); + } + + pthread_mutex_lock(&respool->lock); + + if(respool->numresources >= respool->resalloc) { + // actually respool->resalloc == respool->max + // and numresources should always be smaller + // however just be extra safe here + respool->resalloc += 8; + ResourceDataPrivate **new_res_array = pool_realloc( + respool->pool, + respool->resources, + respool->resalloc * sizeof(ResourceDataPrivate*)); + if(new_res_array) { + respool->resources = new_res_array; + } else { + log_ereport(LOG_FAILURE, "resourcepool_free: OOM"); + resourcepool_destroy_resource(res); + pthread_mutex_unlock(&respool->lock); + return; + } + } + + respool->resources[respool->numresources++] = res; + + pthread_cond_signal(&respool->available); + pthread_mutex_unlock(&respool->lock); +} + +void resourcepool_destroy_resource(ResourceDataPrivate *res) { + res->data.resourcepool->numcreated--; + res->data.resourcepool->type->freeresource(res->data.resourcepool->data, res->resdata); + pool_free(res->data.resourcepool->pool, res); +} + +void resourcepool_destroy(ResourcePool *respool) { + // TODO +} diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/resourcepool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/daemon/resourcepool.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,125 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WS_RESOURCEPOOL_H +#define WS_RESOURCEPOOL_H + +#include "../public/nsapi.h" + +#include "config.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ResourceDataPrivate ResourceDataPrivate; + +struct ResourceDataPrivate { + ResourceData data; + + /* + * the void* pointer returned by respool->type->createresource + * + * ResourceData.data contains the pointer returned by + * respool->type->getresourcedata(resdata)) + */ + void *resdata; +}; + +struct ResourcePool { + /* + * Memory pool + */ + pool_handle_t *pool; + + /* + * Type information of this resource pool + * The type object is stored in the registered types map and should not + * be freed, when the resource pool is destroyed + */ + ResourceType *type; + + /* + * Data returned by the ResourceType init function + * When the pool is destroyed, the data should be passed to + * ResourceType.destroy + */ + void *data; + + pthread_mutex_t lock; + pthread_cond_t available; + + /* + * Array of available resources + * each entry is created with ResourceType.createresource + */ + ResourceDataPrivate **resources; + + /* + * Allocated size of the resources array + */ + size_t resalloc; + + /* + * Number of currently available resources in the array + */ + size_t numresources; + + + /* + * Number of created resources (in use + available) + */ + size_t numcreated; + + /* + * resource pool min parameter + */ + int min; + + /* + * resource pool max parameter + */ + int max; +}; + +int init_resource_pools(void); + +int resourcepool_new(ServerConfiguration *cfg, scstr_t type, scstr_t name, ConfigNode *node); + +void resourcepool_destroy_resource(ResourceDataPrivate *res); + +void resourcepool_destroy(ResourcePool *respool); + +#ifdef __cplusplus +} +#endif + +#endif /* WS_RESOURCEPOOL_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/session.c --- a/src/server/daemon/session.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/session.c Sat Sep 24 16:26:10 2022 +0200 @@ -40,3 +40,8 @@ NSAPISession *sn = (NSAPISession*)s; return sn->config; } + +NSAPI_PUBLIC void* session_get_allocator(Session *sn) { + return &((NSAPISession*)sn)->allocator; +} + diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/session.h --- a/src/server/daemon/session.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/session.h Sat Sep 24 16:26:10 2022 +0200 @@ -47,6 +47,12 @@ threadpool_t *currentpool; threadpool_t *defaultpool; + char *buffer; + int pos; + int cursize; + + UcxAllocator allocator; + ServerConfiguration *config; }; @@ -57,6 +63,8 @@ // get the server configuration of this session NSAPI_PUBLIC void* session_get_config(Session *s); +NSAPI_PUBLIC void* session_get_allocator(Session *sn); + #ifdef __cplusplus } #endif diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/sessionhandler.c --- a/src/server/daemon/sessionhandler.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/sessionhandler.c Sat Sep 24 16:26:10 2022 +0200 @@ -126,6 +126,7 @@ SessionHandler* create_basic_session_handler() { BasicSessionHandler *handler = malloc(sizeof(BasicSessionHandler)); handler->threadpool = threadpool_new(4, 8); + threadpool_start(handler->threadpool); // TODO: handle error handler->sh.enqueue_connection = basic_enq_conn; handler->sh.keep_alive = basic_keep_alive; handler->sh.create_iostream = create_connection_iostream; @@ -351,6 +352,9 @@ if(state == 2) { // parse error fatal_error(request, 400); + log_ereport(LOG_VERBOSE, "http parser: bad request"); + //printf("\n\n%.*s\n\n", parser->request->netbuf->cursize, parser->request->netbuf->inbuf); + //fflush(stdout); event->finish = evt_request_error; io->error = 2; return 0; diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/srvctrl.c --- a/src/server/daemon/srvctrl.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/srvctrl.c Sat Sep 24 16:26:10 2022 +0200 @@ -37,8 +37,8 @@ #include "../util/systhr.h" -#include "../../ucx/utils.h" -#include "../../ucx/buffer.h" +#include +#include #include "srvctrl.h" diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/srvctrl.h --- a/src/server/daemon/srvctrl.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/srvctrl.h Sat Sep 24 16:26:10 2022 +0200 @@ -31,7 +31,7 @@ #include "../public/nsapi.h" -#include "../../ucx/string.h" +#include #include "config.h" diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/threadpools.c --- a/src/server/daemon/threadpools.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/threadpools.c Sat Sep 24 16:26:10 2022 +0200 @@ -45,7 +45,7 @@ static threadpool_t *default_io_pool; static threadpool_t *last_io_pool; -int create_threadpool(sstr_t name, ThreadPoolConfig *cfg) { +int create_threadpool(scstr_t name, ThreadPoolConfig *cfg) { if(thread_pool_map == NULL) { thread_pool_map = ucx_map_new(16); } @@ -63,7 +63,12 @@ } else { threadpool_t *tp = threadpool_new(cfg->min_threads, cfg->max_threads); - int ret = ucx_map_sstr_put(thread_pool_map, name, tp); + int ret = 0; + if(!threadpool_start(tp)) { + ret = ucx_map_sstr_put(thread_pool_map, name, tp); + } else { + ret = 1; + } if(ret == 0) { num_thrpools++; @@ -77,7 +82,7 @@ } } -int create_io_pool(sstr_t name, int numthreads) { +int create_io_pool(scstr_t name, int numthreads) { if(io_pool_map == NULL) { io_pool_map = ucx_map_new(4); } @@ -110,13 +115,13 @@ cfg.max_threads = 8; cfg.queue_size = 64; cfg.stack_size = 262144; - if(create_threadpool(sstr("default"), &cfg)) { + if(create_threadpool(SC("default"), &cfg)) { return 1; } } if(num_iopools == 0) { - if(create_io_pool(sstr("default"), 8)) { + if(create_io_pool(SC("default"), 8)) { return 1; } } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/threadpools.h --- a/src/server/daemon/threadpools.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/threadpools.h Sat Sep 24 16:26:10 2022 +0200 @@ -45,7 +45,7 @@ int queue_size; } ThreadPoolConfig; -int create_threadpool(sstr_t name, ThreadPoolConfig *cfg); +int create_threadpool(scstr_t name, ThreadPoolConfig *cfg); int check_thread_pool_cfg(); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/vfs.c --- a/src/server/daemon/vfs.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/vfs.c Sat Sep 24 16:26:10 2022 +0200 @@ -45,16 +45,19 @@ #define VFS_MALLOC(pool, size) pool ? pool_malloc(pool, size) : malloc(size) #define VFS_FREE(pool, ptr) pool ? pool_free(pool, ptr) : free(ptr) -static UcxMap *vfs_map; +static UcxMap *vfs_type_map; static VFS sys_vfs = { sys_vfs_open, sys_vfs_stat, sys_vfs_fstat, sys_vfs_opendir, + sys_vfs_fdopendir, sys_vfs_mkdir, sys_vfs_unlink, - VFS_CHECKS_ACL + sys_vfs_rmdir, + VFS_CHECKS_ACL, + NULL }; static VFS_IO sys_file_io = { @@ -66,8 +69,9 @@ sys_file_close, //sys_file_aioread, //sys_file_aiowrite, - NULL, - NULL + NULL, // aioread + NULL, // aiowrite + NULL // getetag }; static VFS_DIRIO sys_dir_io = { @@ -75,21 +79,58 @@ sys_dir_close }; -int vfs_init() { - vfs_map = ucx_map_new(16); - if(!vfs_map) { +int vfs_init(void) { + vfs_type_map = ucx_map_new(16); + if(!vfs_type_map) { return -1; } return 0; } -void vfs_add(char *name, VFS *vfs) { +int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate) { WS_ASSERT(name); - if(!vfs_map) { - vfs_init(); + if(!vfs_type_map) { + if(vfs_init()) { + return 1; + } + } + + VfsType *vfsType = malloc(sizeof(VfsType)); + if(!vfsType) { + return 1; } - ucx_map_cstr_put(vfs_map, name, vfs); + vfsType->init = vfsInit; + vfsType->create = vfsCreate; + + return ucx_map_cstr_put(vfs_type_map, name, vfsType); +} + +VfsType* vfs_get_type(scstr_t vfs_class) { + return ucx_map_sstr_get(vfs_type_map, vfs_class); +} + +void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error) { + *error = 0; + if(vfs_class->init) { + void *initData = vfs_class->init(cfg, pool, config); + if(!initData) { + *error = 1; + } + return initData; + } else { + return NULL; + } +} + +VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData) { + VfsType *vfsType = ucx_map_cstr_get(vfs_type_map, vfs_class); + if(!vfsType) { + log_ereport(LOG_MISCONFIG, "vfs_create: unkown VFS type %s", vfs_class); + return NULL; + } + + return vfsType->create(sn, rq, pb, initData); } VFSContext* vfs_request_context(Session *sn, Request *rq) { @@ -97,6 +138,9 @@ WS_ASSERT(rq); VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext)); + if(!ctx) { + return NULL; + } ctx->sn = sn; ctx->rq = rq; ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs; @@ -105,10 +149,11 @@ ctx->aclreqaccess = rq->aclreqaccess; ctx->pool = sn->pool; ctx->vfs_errno = 0; + ctx->error_response_set = 0; return ctx; } -SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) { +SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -125,23 +170,26 @@ } } SYS_FILE file = ctx->vfs->open(ctx, path, oflags); - ctx->aclreqaccess = m; // restore original access mask + ctx->aclreqaccess = m; // restore original access mask + if(!file && ctx) { + sys_set_error_status(ctx); + } return file; } -SYS_FILE vfs_openRO(VFSContext *ctx, char *path) { +SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_RDONLY); } -SYS_FILE vfs_openWO(VFSContext *ctx, char *path) { +SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_WRONLY | O_CREAT); } -SYS_FILE vfs_openRW(VFSContext *ctx, char *path) { +SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) { return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT); } -int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { +int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -159,6 +207,9 @@ } int ret = ctx->vfs->stat(ctx, path, buf); ctx->aclreqaccess = m; // restore original access mask + if(ret && ctx) { + sys_set_error_status(ctx); + } return ret; } @@ -167,7 +218,20 @@ WS_ASSERT(fd); WS_ASSERT(buf); - return ctx->vfs->fstat(ctx, fd, buf); + int ret = ctx->vfs->fstat(ctx, fd, buf); + if(ret && ctx) { + sys_set_error_status(ctx); + } + return ret; +} + +const char * vfs_getetag(SYS_FILE fd) { + WS_ASSERT(fd); + + if(fd->io->opt_getetag) { + return fd->io->opt_getetag(fd); + } + return NULL; } void vfs_close(SYS_FILE fd) { @@ -181,7 +245,7 @@ } } -VFS_DIR vfs_opendir(VFSContext *ctx, char *path) { +VFS_DIR vfs_opendir(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); @@ -199,6 +263,33 @@ } VFS_DIR dir = ctx->vfs->opendir(ctx, path); ctx->aclreqaccess = m; // restore original access mask + if(!dir && ctx) { + sys_set_error_status(ctx); + } + return dir; +} + +VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + WS_ASSERT(ctx); + WS_ASSERT(path); + + uint32_t access_mask = ctx->aclreqaccess | ACL_LIST; + + // ctx->aclreqaccess should be the complete access mask + uint32_t m = ctx->aclreqaccess; // save original access mask + ctx->aclreqaccess = access_mask; // set mask for vfs->open call + if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) { + // VFS does not evaluates the ACL itself, so we have to do it here + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { + return NULL; + } + } + VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd); + ctx->aclreqaccess = m; // restore original access mask + if(!dir && ctx) { + sys_set_error_status(ctx); + } return dir; } @@ -227,23 +318,29 @@ } } -int vfs_mkdir(VFSContext *ctx, char *path) { +int vfs_mkdir(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE); } -int vfs_unlink(VFSContext *ctx, char *path) { +int vfs_unlink(VFSContext *ctx, const char *path) { WS_ASSERT(ctx); WS_ASSERT(path); return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE); } +int vfs_rmdir(VFSContext *ctx, const char *path) { + WS_ASSERT(ctx); + WS_ASSERT(path); + + return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE); +} // private -int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) { +int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) { uint32_t access_mask = ctx->aclreqaccess; access_mask |= access; @@ -259,12 +356,15 @@ } int ret = op(ctx, path); ctx->aclreqaccess = m; // restore original access mask + if(ret && ctx) { + sys_set_error_status(ctx); + } return ret; } /* system vfs implementation */ -SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags) { +SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) { uint32_t access_mask = ctx->aclreqaccess; pool_handle_t *pool = ctx->pool; @@ -313,7 +413,7 @@ return file; } -int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { +int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { uint32_t access_mask = ctx->aclreqaccess; // check ACLs @@ -353,7 +453,7 @@ return 0; } -VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path) { +VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path) { uint32_t access_mask = ctx->aclreqaccess; pool_handle_t *pool = ctx->pool; @@ -386,6 +486,9 @@ DIR *sys_dir = fdopendir(dir_fd); #endif if(!sys_dir) { + if(dir_fd > 0) { + close(dir_fd); + } if(ctx) { ctx->vfs_errno = errno; sys_set_error_status(ctx); @@ -422,16 +525,76 @@ return dir; } -int sys_vfs_mkdir(VFSContext *ctx, char *path) { +VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + uint32_t access_mask = ctx->aclreqaccess; + pool_handle_t *pool = ctx->pool; + + // check ACLs + SysACL sysacl; + if(sys_acl_check(ctx, access_mask, &sysacl)) { + return NULL; + } + + if(sysacl.acl) { + if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) { + acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); + return NULL; + } + } + + // open directory + DIR *sys_dir = fdopendir(fd->fd); + if(!sys_dir) { + if(ctx) { + ctx->vfs_errno = errno; + sys_set_error_status(ctx); + } + return NULL; + } + + SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir)); + if(!dir_data) { + closedir(sys_dir); + return NULL; + } + long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX); + size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1; + dir_data->cur = VFS_MALLOC(pool, entry_len); + if(!dir_data->cur) { + closedir(sys_dir); + VFS_FREE(pool, dir_data); + return NULL; + } + dir_data->dir = sys_dir; + + VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir)); + if(!dir) { + closedir(sys_dir); + VFS_FREE(pool, dir_data->cur); + VFS_FREE(pool, dir_data); + return NULL; + } + dir->ctx = ctx; + dir->data = dir_data; + dir->fd = fd->fd; + dir->io = &sys_dir_io; + return dir; +} + +int sys_vfs_mkdir(VFSContext *ctx, const char *path) { return sys_path_op(ctx, path, sys_mkdir); } -int sys_vfs_unlink(VFSContext *ctx, char *path) { +int sys_vfs_unlink(VFSContext *ctx, const char *path) { return sys_path_op(ctx, path, sys_unlink); } +int sys_vfs_rmdir(VFSContext *ctx, const char *path) { + return sys_path_op(ctx, path, sys_rmdir); +} -int sys_path_op(VFSContext *ctx, char *path, sys_op_f op) { + +int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) { uint32_t access_mask = ctx->aclreqaccess; // check ACLs @@ -484,9 +647,10 @@ } void sys_set_error_status(VFSContext *ctx) { - if(ctx->sn && ctx->rq) { + if(ctx->sn && ctx->rq && !ctx->error_response_set) { int status = util_errno2status(ctx->vfs_errno); protocol_status(ctx->sn, ctx->rq, status, NULL); + ctx->error_response_set = TRUE; } } @@ -539,6 +703,7 @@ entry->name = name; if(getstat) { // TODO: check ACLs again for new path + entry->stat_errno = 0; if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) { entry->stat_errno = errno; } @@ -567,7 +732,7 @@ } } -int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) { +int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) { mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; int ret = mkdir(path, mode); if(ret == 0) { @@ -580,10 +745,14 @@ return ret; } -int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) { +int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) { return unlink(path); } +int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) { + return rmdir(path); +} + /* public file api */ NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) { diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/vfs.h --- a/src/server/daemon/vfs.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/vfs.h Sat Sep 24 16:26:10 2022 +0200 @@ -32,10 +32,17 @@ #include "../public/vfs.h" #include "acl.h" +#include + #ifdef __cplusplus extern "C" { #endif +typedef struct VfsType { + vfs_init_func init; + vfs_create_func create; +} VfsType; + typedef struct SysVFSDir { DIR *dir; struct dirent *cur; @@ -47,20 +54,25 @@ }; typedef enum VFSAioOp VFSAioOp; -int vfs_init(); +int vfs_init(void); +VfsType* vfs_get_type(scstr_t vfs_class); -typedef int(*vfs_op_f)(VFSContext *, char *); -typedef int(*sys_op_f)(VFSContext *, char *, SysACL *); -int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access); +void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error); + +typedef int(*vfs_op_f)(VFSContext *, const char *); +typedef int(*sys_op_f)(VFSContext *, const char *, SysACL *); +int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access); -SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags); -int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf); +SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags); +int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf); int sys_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf); -VFS_DIR sys_vfs_opendir(VFSContext *ctx, char *path); -int sys_vfs_mkdir(VFSContext *ctx, char *path); -int sys_vfs_unlink(VFSContext *ctx, char *path); +VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path); +VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd); +int sys_vfs_mkdir(VFSContext *ctx, const char *path); +int sys_vfs_unlink(VFSContext *ctx, const char *path); +int sys_vfs_rmdir(VFSContext *ctx, const char *path); -int sys_path_op(VFSContext *ctx, char *path, sys_op_f op); +int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op); int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *externacl); void sys_set_error_status(VFSContext *ctx); @@ -76,8 +88,9 @@ int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat); void sys_dir_close(VFS_DIR dir); -int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl); -int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl); +int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl); +int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl); +int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl); void vfs_queue_aio(aiocb_s *aiocb, VFSAioOp op); diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/vserver.c --- a/src/server/daemon/vserver.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/vserver.c Sat Sep 24 16:26:10 2022 +0200 @@ -38,23 +38,6 @@ return vs; } -VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool) { - VirtualServer *newvs = malloc(sizeof(VirtualServer)); - newvs->ref = 1; - newvs->document_root = sstrdup_pool(pool, vs->document_root); - newvs->host = sstrdup_pool(pool, vs->host); - newvs->name = sstrdup_pool(pool, vs->name); - newvs->objectfile = sstrdup_pool(pool, vs->objectfile); - newvs->acls = vs->acls; - acl_data_ref(newvs->acls); - newvs->log = vs->log; // TODO: ref - - newvs->objects = vs->objects; - - return newvs; -} - - diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/vserver.h --- a/src/server/daemon/vserver.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/vserver.h Sat Sep 24 16:26:10 2022 +0200 @@ -58,7 +58,6 @@ }; VirtualServer* vs_new(); -VirtualServer* vs_copy(VirtualServer *vs, pool_handle_t *pool); #ifdef __cplusplus diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/webserver.c --- a/src/server/daemon/webserver.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/webserver.c Sat Sep 24 16:26:10 2022 +0200 @@ -48,7 +48,7 @@ #include "../util/io.h" #include "../util/util.h" -#include "../../ucx/utils.h" +#include #include "../safs/common.h" @@ -60,6 +60,7 @@ #include "log.h" #include "auth.h" #include "srvctrl.h" +#include "resourcepool.h" extern struct FuncStruct webserver_funcs[]; @@ -78,6 +79,11 @@ func_init(); add_functions(webserver_funcs); + // init resource pools + if(init_resource_pools()) { + return -1; + } + // load init.conf if(load_init_conf("config/init.conf")) { return -1; @@ -87,7 +93,6 @@ init_configuration_manager(); ServerConfiguration *cfg; if(cfgmgr_load_config(&cfg) != 0) { - fprintf(stderr, "Cannot load configuration\n"); return -1; } diff -r 21274e5950af -r a1f4cb076d2f src/server/daemon/ws-fn.c --- a/src/server/daemon/ws-fn.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/daemon/ws-fn.c Sat Sep 24 16:26:10 2022 +0200 @@ -70,5 +70,7 @@ { "set-variable", set_variable, NULL, NULL, 0}, { "common-log", common_log, NULL, NULL, 0}, { "send-cgi", send_cgi, NULL, NULL, 0}, + { "webdav-init", webdav_init, NULL, NULL, 0}, + { "webdav-service", webdav_service, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, 0} }; diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,69 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2022 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../../../.. + +include $(BUILD_ROOT)/config.mk + +# list of source files +SRC = init.c +SRC += resource.c +SRC += service.c +SRC += vfs.c +SRC += webdav.c +SRC += config.c + +TEST_SRC = pgtest.c + +OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/server/plugins/postgresql/%$(OBJ_EXT)) + +TEST_OBJ = $(TEST_SRC:%.c=$(BUILD_ROOT)/build/server/plugins/postgresql/%$(OBJ_EXT)) + +BUILD_DIR = $(BUILD_ROOT)/build/server/plugins/postgresql + +PLUGIN_TARGET = $(BUILD_ROOT)/build/lib/libwspostgresql$(LIB_EXT) +TEST_TARGET = $(BUILD_ROOT)/build/lib/libwspgtest$(LIB_EXT) + +PGTEST = $(BUILD_ROOT)/build/server/plugins/postgresql/test + +all: $(PLUGIN_TARGET) $(TEST_TARGET) $(PGTEST) + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(PLUGIN_TARGET): $(BUILD_DIR) $(OBJ) + $(CC) $(POSTGRESQL_LDFLAGS) $(SHLIB_LDFLAGS) -o $@ $(OBJ) + +$(TEST_TARGET): $(BUILD_DIR) $(OBJ) $(TEST_OBJ) + $(CC) $(POSTGRESQL_LDFLAGS) $(SHLIB_LDFLAGS) -o $@ $(OBJ) $(TEST_OBJ) + +$(BUILD_DIR)/%.o: %.c + $(CC) $(CFLAGS) $(POSTGRESQL_CFLAGS) $(SHLIB_CFLAGS) -c -o $@ $< + +$(PGTEST): $(OBJ) + cd test; $(MAKE) diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/config.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/config.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,394 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "../../util/util.h" + +#include + +#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) + +static int pg_load_ext_dav_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file); + + +static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;"; + + +// Uses a PGconn to lookup the resource id of the specified root node +// if the lookup succeeds, the resource id is written to rootid +// in case of an error, an error message will be logged +// returns: 0 success, 1 error +int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) { + PGconn *connection = res->data; + + PGresult *result = PQexecParams( + connection, + sql_get_repository_root, + 1, // number of parameters + NULL, + &rootnode, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(!result) { + log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection)); + return 1; + } + + int ret = 0; + + int nrows = PQntuples(result); + if(nrows == 1) { + char *resource_id_str = PQgetvalue(result, 0, 0); + if(resource_id_str) { + if(!util_strtoint(resource_id_str, rootid)) { + log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode); + ret = 1; + } + } + } else { + log_ereport(LOG_FAILURE, "pg: cannot find root resource '%s'", rootnode); + ret = 1; + } + PQclear(result); + + return ret; +} + +PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { + UcxAllocator a = util_pool_allocator(pool); + + ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql")); + if(!pg) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object"); + return NULL; + } + + scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool")); + scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId")); + scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode")); + scstr_t cfg_dav = serverconfig_directive_value(pg, SC("PGDavConfig")); + + // minimum requirement is a resource pool + if(cfg_respool.length == 0) { + return NULL; + } + + // check rootid + int64_t root_id = 1; + if(cfg_rootid.length > 0) { + if(!util_strtoint(cfg_rootid.ptr, &root_id)) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr); + return NULL; + } + } + + // warn if RootId and RootNode are specified + if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) { + log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored"); + } else if(cfg_rootnode.length > 0) { + // check root node + + // resolve rootnode + // therefore we first need to get a connection from the resourcepool + ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0); + if(!res) { + log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed"); + return NULL; + } + // do lookup, if successful, root_id will be set + int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id); + // we don't need the connection anymore + resourcepool_free(NULL, NULL, res); + if(lookup_err) { + // no logging required, pg_lookup_root will log the error + return NULL; + } + } + + PgRepository *repo = pool_malloc(pool, sizeof(PgRepository)); + ZERO(repo, sizeof(PgRepository)); + + repo->resourcepool = sstrdup_a(&a, cfg_respool); + repo->root_resource_id = root_id; + + // check for extended pg dav config + if(cfg_dav.length > 0) { + // load extended config from config file + char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr); + if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) { + // error + repo = NULL; // no need to cleanup because everything is from the pool + } + free(cfg_file_path); + } + + return repo; +} + +static int pg_ext_get_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *root); +static int pg_ext_get_extension( + PgExtParser *ext, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *ext_node); + +static const char* pg_util_xml_get_text(const xmlNode *elm) { + xmlNode *node = elm->children; + while(node) { + if(node->type == XML_TEXT_NODE) { + return (const char*)node->content; + } + node = node->next; + } + return NULL; +} + +// load additional postgresql webdav config from the specified xml file +static int pg_load_ext_dav_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path) +{ + UcxAllocator a = util_pool_allocator(pool); + + // check if the file exists and can be accessed + struct stat s; + if(stat(file_path, &s)) { + if(errno == ENOENT) { + log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path); + } else { + log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path); + } + + return 1; + } + + // prepare PgRepository + repo->prop_ext = ucx_map_new_a(&a, 8); + if(!repo->prop_ext) { + log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM"); + return 1; + } + + // load xml document + xmlDoc *doc = xmlReadFile(file_path, NULL, 0); + if(!doc) { + log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path); + return 1; + } + + // the root element must be + int ret = 0; + xmlNode *xml_root = xmlDocGetRootElement(doc); + if(xstreq(xml_root->name, "repository")) { + // parse config + ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root); + } else { + log_ereport(LOG_MISCONFIG, "pg: config %s: root element expected", file_path); + ret = 1; + } + xmlFreeDoc(doc); + + return ret; +} + + + + +static int pg_ext_get_config( + ServerConfiguration *cfg, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *root) +{ + xmlNode *node = root->children; + int ret = 0; + + PgExtParser parserData; + parserData.table_lookup = ucx_map_new(8); + parserData.tables = NULL; + + while(node && !ret) { + // currently, the only possible config element is + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "extension")) { + ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node); + } + } + node = node->next; + } + + // convert parserData + if(!ret) { + size_t ntables = ucx_list_size(parserData.tables); + repo->ntables = ntables; + repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); + if(repo->tables) { + int i = 0; + UCX_FOREACH(elm, parserData.tables) { + PgExtTable *tab = elm->data; + repo->tables[i++] = *tab; + } + } else { + ret = 1; + } + + } + + // cleanup parser + ucx_list_free_content(parserData.tables, free); + ucx_list_free(parserData.tables); + ucx_map_free(parserData.table_lookup); + + return ret; +} + +static int pg_ext_get_extension( + PgExtParser *ext, + pool_handle_t *pool, + PgRepository *repo, + const char *file_path, + xmlNode *ext_node) +{ + UcxAllocator a = util_pool_allocator(pool); + + xmlNode *node = ext_node->children; + + const char *table = NULL; + UcxList *properties = NULL; + + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "table")) { + const char *value = pg_util_xml_get_text(node); + if(!value) { + log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table); + return 1; + } else if(table) { + log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table); + return 1; + } + table = value; + } else if(xstreq(node->name, "properties")) { + // add all child elements to the properties list + xmlNode *ps = node->children; + while(ps) { + if(ps->type == XML_ELEMENT_NODE) { + // validate + // required: namespace, value + if(!ps->ns || !ps->ns->href) { + log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name); + return 1; + } + const char *value = pg_util_xml_get_text(ps); + if(!value) { + log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name); + return 1; + } + properties = ucx_list_append_a(&a, properties, ps); + } + ps = ps->next; + } + } + } + node = node->next; + } + + // check if anything is missing + if(!table) { + log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path); + return 1; + } + if(!properties) { + log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path); + return 1; + } + + // check if the table was already specified + if(ucx_map_cstr_get(ext->table_lookup, table)) { + log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table); + return 1; + } + // mark table as used + // tabname will be used later, so ist must be allocated in the pool + char *tabname = pool_strdup(pool, table); + if(!tabname) return 1; + + // exttable is only used temporarily + PgExtTable *exttable = malloc(sizeof(PgExtTable)); + if(!exttable) return 1; + exttable->table = tabname; + exttable->isused = 0; // not relevant in config + int tableindex = (int)ucx_list_size(ext->tables); + ext->tables = ucx_list_append(ext->tables, exttable); + + if(ucx_map_cstr_put(ext->table_lookup, table, table)) { + return 1; + } + + UCX_FOREACH(elm, properties) { + xmlNode *ps = elm->data; + const char *value = pg_util_xml_get_text(ps); + + PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt)); + if(!ext_col) { + return 1; + } + ext_col->tableindex = tableindex; + ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href); + ext_col->name = pool_strdup(pool, (const char*)ps->name); + ext_col->column = pool_strdup(pool, (const char*)value); + if(!ext_col->ns || !ext_col->name || !ext_col->column) { + return 1; + } + + UcxKey key = webdav_property_key(ext_col->ns, ext_col->name); + int err = ucx_map_put(repo->prop_ext, key, ext_col); + free((void*)key.data); + if(err) { + return 1; + } + } + + return 0; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/config.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/config.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef WS_PG_CONFIG_H +#define WS_PG_CONFIG_H + +#include "../../public/nsapi.h" +#include "../../public/webdav.h" + +#include "../../daemon/config.h" +#include "../../config/serverconfig.h" + +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *table; + WSBool isused; +} PgExtTable; + +typedef struct PgRepository { + int64_t root_resource_id; + sstr_t resourcepool; + PgExtTable *tables; + size_t ntables; + UcxMap *prop_ext; +} PgRepository; + +typedef struct { + char *column; + char *ns; + char *name; + int tableindex; +} PgPropertyStoreExt; + +typedef struct { + UcxMap *table_lookup; + UcxList *tables; +} PgExtParser; + +int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid); + +PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); + +#ifdef __cplusplus +} +#endif + +#endif /* WS_PG_CONFIG_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/init.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/init.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "init.h" + +#include "resource.h" +#include "vfs.h" +#include "webdav.h" + +int pg_init(pblock *pb, Session *sn, Request *rq) { + if(resourcepool_register_type("postgresql", pg_get_resource_type())) { + log_ereport(LOG_FAILURE, "pg-init: Cannot register resourcepool type"); + return REQ_ABORTED; + } + + if(pg_register_vfs(pb)) { + log_ereport(LOG_FAILURE, "pg-init: Cannot register vfs type"); + return REQ_ABORTED; + } + + if(pg_register_webdav_backend(pb)) { + log_ereport(LOG_FAILURE, "pg-init: Cannot register dav type"); + return REQ_ABORTED; + } + + return REQ_PROCEED; +} + +int pg_register_vfs(pblock *pb) { + return vfs_register_type("postgresql", pg_vfs_init, pg_vfs_create); +} + +int pg_register_webdav_backend(pblock *pb) { + if(webdav_register_backend("postgresql", pg_webdav_init, pg_webdav_create)) { + return 1; + } + return 0; +} + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/init.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/init.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WS_PG_INIT_H +#define WS_PG_INIT_H + +#include "../../public/nsapi.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int pg_init(pblock *pb, Session *sn, Request *Rq); + +int pg_register_vfs(pblock *pb); + +int pg_register_webdav_backend(pblock *pb); + + +#ifdef __cplusplus +} +#endif + +#endif /* WS_PG_INIT_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/pgtest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/pgtest.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,911 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "../../util/util.h" +#include "../../test/testutils.h" +#include "../../test/webdav.h" +#include "../../public/nsapi.h" +#include "../../public/webdav.h" +#include "../../webdav/webdav.h" + +#include +#include +#include + +#include "pgtest.h" +#include "vfs.h" +#include "webdav.h" + +#include + +#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) + +static char *pg_connstr = "postgresql://localhost/test1"; +static int abort_pg_tests = 0; +static PGconn *test_connection; +static ResourceData resdata; +static PgRepository test_repo; + +void debug_print_resources(void) { + PGresult *result = PQexec(test_connection, "select * from Resource;"); + int n = PQntuples(result); + printf("\nntuples: %d\n-----------------------------------------------\n", n); + printf("%10s %10s %s\n", "resource_id", "parent_id", "nodename"); + for(int i=0;imp->allocator; + node = node->children; + + sstr_t href = {NULL, 0}; + UcxMap *properties = ucx_map_new_a(ms->mp->allocator, 16); + + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "href")) { + xmlNode *href_node = node->children; + if(href_node->type != XML_TEXT_NODE) { + return; + } + href = sstrdup_a(ms->mp->allocator, scstr((const char*)href_node->content)); + } else if(xstreq(node->name, "propstat")) { + xmlNode *n = node->children; + xmlNode *prop_node = NULL; + int status_code = 0; + while(n) { + if(n->type == XML_ELEMENT_NODE) { + if(xstreq(n->name, "prop")) { + prop_node = n; + } else if(xstreq(n->name, "status")) { + xmlNode *status_node = n->children; + if(status_node->type != XML_TEXT_NODE) { + return; + } + sstr_t status_str = sstr((char*)status_node->content); + if(status_str.length < 13) { + return; + } + status_str = sstrsubsl(status_str, 9, 3); + sstr_t status_s = sstrdup(status_str); + status_code = atoi(status_s.ptr); + free(status_s.ptr); + } + } + n = n->next; + } + + n = prop_node->children; + while(n) { + if(n->type == XML_ELEMENT_NODE) { + TestProperty *property = ucx_mempool_calloc(ms->mp, 1, sizeof(TestProperty)); + if(n->ns) { + property->prefix = n->ns->prefix ? sstrdup_a(a, scstr((const char*)n->ns->prefix)).ptr : NULL; + property->namespace = n->ns->href ? sstrdup_a(a, scstr((const char*)n->ns->href)).ptr : NULL; + } + property->name = sstrdup_a(a, scstr((const char*)n->name)).ptr; + property->node = n; + property->status = status_code; + xmlNode *value = n->children; + if(value && value->type == XML_TEXT_NODE) { + property->value = sstrdup_a(a, scstr((const char*)value->content)).ptr; + } + sstr_t pname = sstrcat(2, sstr(property->namespace), sstr(property->name)); + ucx_map_sstr_put(properties, pname, property); + free(pname.ptr); + } + n = n->next; + } + } + } + node = node->next; + } + + TestResponse *resp =almalloc(a, sizeof(TestResponse)); + resp->href = href.ptr; + resp->properties = properties; + + ucx_map_sstr_put(ms->responses, href, resp); +} + +TestMultistatus* test_parse_multistatus(const char *space, size_t size) { + xmlDoc *doc = xmlReadMemory(space, size, NULL, NULL, 0); + if(!doc) { + return NULL; + } + + UcxMempool *mp = ucx_mempool_new(64); + TestMultistatus *ms = ucx_mempool_malloc(mp, sizeof(TestMultistatus)); + ms->doc = doc; + ms->mp = mp; + ms->responses = ucx_map_new_a(mp->allocator, 8); + + // parse response + xmlNode *xml_root = xmlDocGetRootElement(doc); + xmlNode *node = xml_root->children; + while(node) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "response")) { + parse_response_tag(ms, node); + } + } + node = node->next; + } + + return ms; +} + + +void test_multistatus_destroy(TestMultistatus *ms) { + if(!ms) return; + xmlFreeDoc(ms->doc); + ucx_mempool_destroy(ms->mp); +} + + +UCX_TEST(test_pg_conn) { + char *msg = test_connection ? PQerrorMessage(test_connection) : "no connection"; + + UCX_TEST_BEGIN; + + if(abort_pg_tests) { + int msglen = strlen(msg); + if(msglen > 0 && msg[msglen-1] == '\n') { + msglen--; + } + fprintf(stdout, "%.*s: ", msglen, msg); + UCX_TEST_ASSERT(1 == 0, "skip pg tests"); + } else { + UCX_TEST_ASSERT(1 == 1, "ok"); + } + + UCX_TEST_END; +} + +UCX_TEST(test_pg_lookup_root) { + UCX_TEST_BEGIN; + + // test already done in test_root_lookup() + UCX_TEST_ASSERT(!abort_pg_tests, "Lookup failed"); + + UCX_TEST_END; +} + + +static VFS* create_test_pgvfs(Session *sn, Request *rq) { + return pg_vfs_create_from_resourcedata(sn, rq, &test_repo, &resdata); +} + + +UCX_TEST(test_pg_vfs_open) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + SYS_FILE file; + + UCX_TEST_BEGIN; + + file = vfs_open(vfs, "/test_notfound1", O_RDONLY); + UCX_TEST_ASSERT(!file, "/test_notfound should not exist"); + + file = vfs_open(vfs, "/test_file1", O_RDWR | O_CREAT); + UCX_TEST_ASSERT(file, "cannot create file 1"); + + vfs_close(file); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_vfs_io) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + SYS_FILE file; + SYS_FILE file2; + + UCX_TEST_BEGIN; + + file = vfs_open(vfs, "/test_f1", O_WRONLY | O_CREAT); + UCX_TEST_ASSERT(file, "cannot open file1"); + + int w = system_fwrite(file, "test1\n", 6); + UCX_TEST_ASSERT(w == 6, "fwrite ret (1)"); + w = system_fwrite(file, "2", 1); + UCX_TEST_ASSERT(w == 1, "fwrite ret (2)"); + + vfs_close(file); + + file = vfs_open(vfs, "/test_f1", O_RDONLY); + file2 = vfs_open(vfs, "/test_f2", O_WRONLY | O_CREAT); + UCX_TEST_ASSERT(file, "cannot open file1"); + UCX_TEST_ASSERT(file2, "cannot open file2"); + + char buf[128]; + int r = system_fread(file, buf, 128); + UCX_TEST_ASSERT(r == 7, "cannot read from file1"); + + w = system_fwrite(file2, buf, r); + UCX_TEST_ASSERT(w == 7, "cannot write to file2"); + + vfs_close(file); + vfs_close(file2); + + file2 = vfs_open(vfs, "/test_f2", O_RDONLY); + + r = system_fread(file, buf, 128); + UCX_TEST_ASSERT(r == 7, "fread ret"); + UCX_TEST_ASSERT(!memcmp(buf, "test1\n2", 7), "wrong buffer content after read"); + + vfs_close(file2); + + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_vfs_stat) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + // testdata, content doesn't matter + char test1[512]; + memset(test1, 'x', 512); + const int test_len1 = 200; + const int test_len2 = 432; + + SYS_FILE f1 = vfs_open(vfs, "/test_s1", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "cannot open test_s1"); + system_fwrite(f1, test1, test_len1); + vfs_close(f1); + + SYS_FILE f2 = vfs_open(vfs, "/test_s2", O_RDWR|O_CREAT); + UCX_TEST_ASSERT(f2, "cannot open test_s2"); + system_fwrite(f2, test1, test_len2); + vfs_close(f2); + + struct stat st1, st2; + int r1 = vfs_stat(vfs, "/test_s1", &st1); + int r2 = vfs_stat(vfs, "/test_s2", &st2); + + UCX_TEST_ASSERT(r1 == 0, "stat1 failed"); + UCX_TEST_ASSERT(r2 == 0, "stat2 failed"); + + UCX_TEST_ASSERT(st1.st_size == test_len1, "s1 wrong length"); + UCX_TEST_ASSERT(st2.st_size == test_len2, "s2 wrong length"); + + int testfail = vfs_stat(vfs, "/test_stat_fail", &st1); + UCX_TEST_ASSERT(testfail != 0, "stat 3 should fail"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_vfs_mkdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + struct stat s; + + SYS_FILE f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1 == NULL, "open should fail"); + + int r = vfs_mkdir(vfs, "/test_mkdir"); + UCX_TEST_ASSERT(r == 0, "mkdir failed"); + + r = vfs_stat(vfs, "/test_mkdir", &s); + UCX_TEST_ASSERT(r == 0, "stat (1) failed"); + + UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir is not a directory"); + + f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT); + vfs_close(f1); + UCX_TEST_ASSERT(f1, "open failed"); + + r = vfs_stat(vfs, "/test_mkdir/file", &s); + UCX_TEST_ASSERT(r == 0, "stat (2) failed"); + + r = vfs_mkdir(vfs, "/test_mkdir/test_sub"); + UCX_TEST_ASSERT(r == 0, "mkdir failed (2)"); + + r = vfs_stat(vfs, "/test_mkdir/test_sub", &s); + UCX_TEST_ASSERT(r == 0, "stat (3) failed"); + UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub is not a directory"); + + r = vfs_mkdir(vfs, "/test_mkdir/test_sub/test_sub2/"); + UCX_TEST_ASSERT(r == 0, "mkdir failed (4)"); + + r = vfs_stat(vfs, "/test_mkdir/test_sub/test_sub2/", &s); + UCX_TEST_ASSERT(r == 0, "stat (4) failed"); + UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub/test_sub2/ is not a directory"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_vfs_unlink) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + SYS_FILE f1 = vfs_open(vfs, "/test_unlink1", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "cannot create test file"); + system_fwrite(f1, "test", 4); + + PgFile *pgfile = f1->data; + Oid oid = pgfile->oid; + + vfs_close(f1); + + int r = vfs_unlink(vfs, "/test_unlink1"); + UCX_TEST_ASSERT(r == 0, "unlink failed"); + + f1 = vfs_open(vfs, "/test_unlink1", O_RDONLY); + UCX_TEST_ASSERT(f1 == NULL, "test file not deleted"); + + PGresult *result = PQexec(test_connection, "savepoint sp;"); + PQclear(result); + int pgfd = lo_open(test_connection, oid, INV_READ); + UCX_TEST_ASSERT(pgfd < 0, "large object not deleted"); + result = PQexec(test_connection, "rollback to savepoint sp;"); + PQclear(result); + + r = vfs_unlink(vfs, "/test_unlink1"); + UCX_TEST_ASSERT(r, "unlink should fail"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_vfs_rmdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + + PQexec(test_connection, "delete from Resource where parent_id is not null;"); + + UCX_TEST_BEGIN; + + int r; + SYS_FILE f1; + + // prepare some dirs/files + r = vfs_mkdir(vfs, "/rmdir_test"); + UCX_TEST_ASSERT(r == 0, "mkdir failed (1)"); + r = vfs_mkdir(vfs, "/rmdir_test/subdir1"); + UCX_TEST_ASSERT(r == 0, "mkdir failed (2)"); + r = vfs_mkdir(vfs, "/rmdir_test/subdir2"); + UCX_TEST_ASSERT(r == 0, "mkdir failed (3)"); + + f1 = vfs_open(vfs, "/rmdir_test/subdir2/file", O_CREAT|O_WRONLY); + UCX_TEST_ASSERT(f1, "open failed"); + vfs_close(f1); + + // test rmdir + r = vfs_rmdir(vfs, "/rmdir_test/subdir1"); + UCX_TEST_ASSERT(r == 0, "rmdir failed");; + + r = vfs_rmdir(vfs, "/rmdir_test/subdir2"); + UCX_TEST_ASSERT(r != 0, "rmdir should fail if the dir is not empty"); + + r = vfs_unlink(vfs, "/rmdir_test/subdir2/file"); + UCX_TEST_ASSERT(r == 0, "unlink failed"); + + r = vfs_rmdir(vfs, "/rmdir_test/subdir2"); + UCX_TEST_ASSERT(r == 0, "rmdir failed 2"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +/* ----------------------------- WebDAV tests ----------------------------- */ + + +static WebdavBackend* create_test_pgdav(Session *sn, Request *rq) { + return pg_webdav_create_from_resdata(sn, rq, &test_repo, &resdata); +} + +UCX_TEST(test_pg_webdav_create_from_resdata) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPFIND", "/"); + + UCX_TEST_BEGIN; + + WebdavBackend *dav = create_test_pgdav(sn, rq); + UCX_TEST_ASSERT(dav, "cannot create pg dav backend"); + + UCX_TEST_END; +} + +UCX_TEST(test_pg_prepare_tests) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = create_test_pgvfs(sn, rq); + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + vfs_mkdir(vfs, "/propfind"); + vfs_mkdir(vfs, "/proppatch"); + SYS_FILE f1; + + int64_t res1_id, res2_id; + + f1 = vfs_open(vfs, "/propfind/res1", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "res1 create failed"); + res1_id = ((PgFile*)f1->data)->resource_id; + vfs_close(f1); + + f1 = vfs_open(vfs, "/propfind/res2", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "res2 create failed"); + res2_id = ((PgFile*)f1->data)->resource_id; + vfs_close(f1); + + f1 = vfs_open(vfs, "/propfind/res3", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "res3 create failed"); + vfs_close(f1); + + int r = vfs_mkdir(vfs, "/propfind/sub"); + UCX_TEST_ASSERT(r == 0, "sub create failed"); + + f1 = vfs_open(vfs, "/propfind/sub/res4", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "res4 create failed"); + vfs_close(f1); + + f1 = vfs_open(vfs, "/proppatch/pp1", O_WRONLY|O_CREAT); + UCX_TEST_ASSERT(f1, "pp1 create failed"); + vfs_close(f1); + + // 2 properties for res1 + char idstr[32]; + snprintf(idstr, 32, "%" PRId64, res1_id); + const char* params[1] = { idstr }; + PGresult *result = PQexecParams( + test_connection, + "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'testvalue');", + 1, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1"); + PQclear(result); + + result = PQexecParams( + test_connection, + "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'prop2', 'value2');", + 1, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1"); + PQclear(result); + + // 1 property for res2 + snprintf(idstr, 32, "%" PRId64, res2_id); + result = PQexecParams( + test_connection, + "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'res2test');", + 1, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1"); + PQclear(result); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_pg_webdav_propfind) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + // test data: + // + // /propfind/ + // /propfind/res1 (2 properties: test, prop2) + // /propfind/res2 (1 property: test) + // /propfind/res3 + // /propfind/sub + // /propfind/sub/res4 + + int ret; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND1); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "0", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed"); + + TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml"); + + TestResponse *r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response"); + + UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count"); + + TestProperty *p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype"); + UCX_TEST_ASSERT(p, "propfind1: missing property 'resourcetype'"); + UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'resourcetype'"); + + p = ucx_map_cstr_get(r1->properties, "DAV:getlastmodified"); + UCX_TEST_ASSERT(p, "propfind1: missing property 'getlastmodified'"); + UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'getlastmodified'"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + // Test 2 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "1", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response"); + + UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/res2"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res2 response"); + + p = ucx_map_cstr_get(r1->properties, "http://example.com/test"); + UCX_TEST_ASSERT(p, "propfind2: missing property 'test'"); + UCX_TEST_ASSERT(p->status == 200, "propfind2: wrong status code for property 'test'"); + UCX_TEST_ASSERT(!strcmp(p->value, "res2test"), "propfind2: wrong property value"); + + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + + // Test 3 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "infinity", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (3) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response"); + + UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count"); + + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res1 response"); + + p = ucx_map_cstr_get(r1->properties, "http://example.com/test"); + UCX_TEST_ASSERT(p, "propfind3: missing property 'test'"); + UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'test'"); + UCX_TEST_ASSERT(!strcmp(p->value, "testvalue"), "propfind3: wrong property value"); + + p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2"); + UCX_TEST_ASSERT(p, "propfind3: missing property 'prop2'"); + UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'prop2'"); + UCX_TEST_ASSERT(!strcmp(p->value, "value2"), "propfind3: wrong property value"); + + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/sub/res4"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res4 response"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + + +UCX_TEST(test_pg_webdav_propfind_allprop) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + // test data: + // + // /propfind/ + // /propfind/res1 (2 properties: test, prop2) + // /propfind/res2 (1 property: test) + // /propfind/res3 + // /propfind/sub + // /propfind/sub/res4 + + int ret; + TestResponse *r1; + TestProperty *p; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "0", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed"); + + TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response"); + UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count"); + + p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype"); + UCX_TEST_ASSERT(r1, "propfind1: missing resourcetype property"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + // Test 2 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "1", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response"); + UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1"); + UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res1 response"); + + p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype"); + UCX_TEST_ASSERT(r1, "propfind2: missing resourcetype property"); + p = ucx_map_cstr_get(r1->properties, "http://example.com/test"); + UCX_TEST_ASSERT(r1, "propfind2: missing test property"); + p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2"); + UCX_TEST_ASSERT(r1, "propfind2: missing prop2 property"); + + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind2: missing /propfind/res2 response"); + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind2: missing /propfind/res3 response"); + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind2: missing /propfind/sub response"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + // Test 3 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP); + rq->davCollection = create_test_pgdav(sn, rq); + pblock_nvinsert("depth", "infinity", rq->headers); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response"); + UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count"); + + r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1"); + UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/res1 response"); + + p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype"); + UCX_TEST_ASSERT(r1, "propfind3: missing resourcetype property"); + p = ucx_map_cstr_get(r1->properties, "http://example.com/test"); + UCX_TEST_ASSERT(r1, "propfind3: missing test property"); + p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2"); + UCX_TEST_ASSERT(r1, "propfind3: missing prop2 property"); + + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind3: missing /propfind/res2 response"); + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind3: missing /propfind/res3 response"); + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind3: missing /propfind/sub response"); + UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/res4"), "propfind3: missing /propfind/sub/res4 response"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + UCX_TEST_END; +} + +UCX_TEST(test_pg_webdav_proppatch_set) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + // test data: + // + // /propfind/ + // /propfind/res1 (2 properties: test, prop2) + // /propfind/res2 (1 property: test) + // /propfind/res3 + // /propfind/sub + // /propfind/sub/res4 + + int ret; + TestResponse *r1; + TestProperty *p; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH1); + rq->davCollection = create_test_pgdav(sn, rq); + + ret = webdav_proppatch(pb, sn, rq); + UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch1 failed"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "proppatch1 response is not valid xml"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + // Test 2: xml property value + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH2); + rq->davCollection = create_test_pgdav(sn, rq); + + ret = webdav_proppatch(pb, sn, rq); + UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch2 failed"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + ms = test_parse_multistatus(st->buf->space, st->buf->size); + UCX_TEST_ASSERT(ms, "proppatch2 response is not valid xml"); + + testutil_destroy_session(sn); + test_multistatus_destroy(ms); + testutil_iostream_destroy(st); + + + UCX_TEST_END; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/pgtest.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/pgtest.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,129 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/* + * File: pgtest.h + * Author: olaf + * + * Created on 16. April 2022, 14:37 + */ + +#ifndef PGTEST_H +#define PGTEST_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TestMultistatus { + UcxMempool *mp; + xmlDoc *doc; + UcxMap *responses; +} TestMultistatus; + +typedef struct TestResponse { + char *href; + UcxMap *properties; +} TestResponse; + +typedef struct TestProperty { + char *prefix; + char *namespace; + char *name; + char *lang; + char *value; + xmlNode *node; + int status; +} TestProperty; + +TestMultistatus* test_parse_multistatus(const char *space, size_t size); +void test_multistatus_destroy(TestMultistatus *ms); + +UCX_TEST(test_pg_conn); + +UCX_TEST(test_pg_lookup_root); + +UCX_TEST(test_pg_vfs_open); +UCX_TEST(test_pg_vfs_io); +UCX_TEST(test_pg_vfs_stat); +UCX_TEST(test_pg_vfs_mkdir); +UCX_TEST(test_pg_vfs_unlink); +UCX_TEST(test_pg_vfs_rmdir); + +UCX_TEST(test_pg_webdav_create_from_resdata); +UCX_TEST(test_pg_prepare_tests); +UCX_TEST(test_pg_webdav_propfind); +UCX_TEST(test_pg_webdav_propfind_allprop); +UCX_TEST(test_pg_webdav_proppatch_set); + + +/* --------------------------- PROPFIND --------------------------- */ + +#define PG_TEST_PROPFIND1 " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define PG_TEST_PROPFIND2 " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define PG_TEST_ALLPROP " \ + \ + \ + " + +#define PG_TEST_PROPPATCH1 " \ + \ + \ + testname \ + \ + " + +#define PG_TEST_PROPPATCH2 " \ + \ + \ + \ + \ + value\ + example\ + \ + \ + \ + \ + \ + \ + " + +#ifdef __cplusplus +} +#endif + +#endif /* PGTEST_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/resource.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/resource.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "resource.h" + +static ResourceType pg_resource_type = { + (resource_pool_init_func)pg_resourcepool_init, + (resource_pool_destroy_func)pg_resourcepool_destroy, + (resource_pool_createresource_func)pg_resourcepool_createresource, + (resource_pool_freeresource_func)pg_resourcepool_freeresource, + (resource_pool_prepare_func)pg_resourcepool_prepare, + (resource_pool_finish_func)pg_resourcepool_finish, + (resource_pool_getresourcedata_func)pg_resourcepool_getresourcedata +}; + +ResourceType* pg_get_resource_type(void) { + return &pg_resource_type; +} + +void * pg_resourcepool_init(pool_handle_t *pool, const char *rpname, pblock *pb) { + char *connection = pblock_findval("Connection", pb); + if(!connection) { + log_ereport(LOG_MISCONFIG, "Resource pool %s: Missing Connection parameter", rpname); + return NULL; + } + + // test connection + PGconn *test_connection = PQconnectdb(connection); + if(pg_check_connection(LOG_WARN, rpname, test_connection)) { + log_ereport(LOG_WARN, "Resource pool %s: Connection check failed", rpname); + } + if(test_connection) PQfinish(test_connection); + + PgResourcePool *pg = pool_malloc(pool, sizeof(PgResourcePool)); + if(!pg) { + return NULL; + } + pg->pool = pool; + pg->name = rpname; + pg->connection = connection; + + return pg; + +} + +int pg_check_connection(int loglevel, const char *rpname, PGconn *connection) { + if(!connection) { + log_ereport(loglevel, "Resource pool %s: Cannot create PQ connection", rpname); + return 1; + } + if(PQstatus(connection) != CONNECTION_OK) { + char *err = PQerrorMessage(connection); + int errlen = 0; + if(err) { + errlen = strlen(err); + if(errlen > 0 && err[errlen-1] == '\n') { + errlen--; + } + } + log_ereport(loglevel, "Resource pool %s: Failed to connect to database: %.*s", rpname, errlen, err); + PQfinish(connection); + return 1; + } + return 0; +} + + +void pg_resourcepool_destroy(PgResourcePool *pg) { + // unused +} + +void * pg_resourcepool_createresource(PgResourcePool *pg) { + PGconn *connection = PQconnectdb(pg->connection); + if(pg_check_connection(LOG_FAILURE, pg->name, connection)) { + return NULL; + } + + PgResource *res = pool_malloc(pg->pool, sizeof(PgResource)); + if(!res) { + PQfinish(connection); + log_ereport(LOG_CATASTROPHE, "pg_resourcepool_createresource: OOM"); + return NULL; + } + res->connection = connection; + + return res; +} + +void pg_resourcepool_freeresource(PgResourcePool *pg, PgResource *res) { + if(res->connection) { + PQfinish(res->connection); + } + pool_free(pg->pool, res); +} + +int pg_resourcepool_prepare(PgResourcePool *pg, PgResource *res) { + PGresult *result = PQexec(res->connection, "BEGIN"); + PQclear(result); // TODO: handle error + return 0; +} + +int pg_resourcepool_finish(PgResourcePool *pg, PgResource *res) { + PGresult *result = PQexec(res->connection, "COMMIT"); + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: COMMIT failed failed: %s", PQerrorMessage(res->connection)); + } + PQclear(result); + return 0; +} + +void * pg_resourcepool_getresourcedata(PgResource *res) { + return res->connection; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/resource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/resource.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WS_PG_RESOURCE_H +#define WS_PG_RESOURCE_H + +#include "../../public/nsapi.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PgResourcePool { + /* + * ResourcePool parameters + */ + pblock *param; + + /* + * Cfg memorypool + */ + pool_handle_t *pool; + + /* + * ResourcePool name + */ + const char *name; + + /* + * connection string used for PQconnectdb + */ + char *connection; + + +} PgResourcePool; + +typedef struct PgResource { + PGconn *connection; +} PgResource; + +ResourceType* pg_get_resource_type(void); + +void * pg_resourcepool_init(pool_handle_t *pool, const char *rpname, pblock *pb); +void pg_resourcepool_destroy(PgResourcePool *pg); +void * pg_resourcepool_createresource(PgResourcePool *pg); +void pg_resourcepool_freeresource(PgResourcePool *pg, PgResource *res); +int pg_resourcepool_prepare(PgResourcePool *pg, PgResource *res); +int pg_resourcepool_finish(PgResourcePool *pg, PgResource *res); +void * pg_resourcepool_getresourcedata(PgResource *res); + +/* + * checks if conn is a working connection and logs an error message if not + * conn can be null (resulting in an error message) + * if conn is not null but the status is not CONNECTION_OK, + * conn will be destroyed + */ +int pg_check_connection(int loglevel, const char *rpname, PGconn *connection); + +#ifdef __cplusplus +} +#endif + +#endif /* WS_PG_RESOURCE_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/service.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/service.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,113 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "service.h" + +#include +#include + +#include "resource.h" + +int pg_query(pblock *pb, Session *sn, Request *rq) { + char *respool = pblock_findval("resource", pb); + char *query = pblock_findval("query", pb); + + if(!respool) { + log_ereport(LOG_MISCONFIG, "pg-query: missing resource parameter"); + return REQ_ABORTED; + } + if(!query) { + log_ereport(LOG_MISCONFIG, "pg-query: missing query parameter"); + return REQ_ABORTED; + } + + // Get the named resource and check if the PG connection works + ResourceData *res = resourcepool_lookup(sn, rq, respool, 0); + if(!res) { + log_ereport(LOG_FAILURE, "pg-query: cannot get resource '%s'", respool); + return REQ_ABORTED; + } + + PGconn *connection = res->data; + if(pg_check_connection(LOG_FAILURE, respool, connection)) { + return REQ_ABORTED; + } + + // execute query + PGresult *result = PQexec(connection, query); + if(!result) return REQ_ABORTED; + + // start response + pblock_remove("content-type", rq->srvhdrs); + pblock_nvinsert("content-type", "text/html", rq->srvhdrs); + + protocol_status(sn, rq, 200, NULL); + http_start_response(sn, rq); + + // (html) header + net_printf(sn->csd, "%s", "\n\n"); + + int nfields = PQnfields(result); + if(nfields > 0) { + net_printf(sn->csd, "\n\n"); + for(int i=0;icsd, "\n", fieldNameEscaped); + FREE(fieldNameEscaped); + } + } + net_printf(sn->csd, "\n"); + + int nrows = PQntuples(result); + for(int r=0;rcsd, "\n"); + for(int c=0;ccsd, "\n", fieldValueEscaped); + FREE(fieldValueEscaped); + } + } + net_printf(sn->csd, "\n"); + } + + + net_printf(sn->csd, "
%s
%s
\n"); + } else { + net_printf(sn->csd, "

%s

\n", PQresStatus(PQresultStatus(result))); + } + + net_printf(sn->csd, "%s", "\n"); + + PQclear(result); + + return REQ_PROCEED; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/service.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/service.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PG_SERVICE_H +#define PG_SERVICE_H + +#include "../../public/nsapi.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * SAF parameter: + * resourcepool: name of the postgresql resource pool + * query: SQL query to execute + */ +int pg_query(pblock *pb, Session *sn, Request *rq); + +#ifdef __cplusplus +} +#endif + +#endif /* PG_SERVICE_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/test/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/test/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,42 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2022 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../../../../.. + +include $(BUILD_ROOT)/config.mk + +BUILD_DIR = $(BUILD_ROOT)/build/server/plugins/postgresql + +PGTEST = $(BUILD_ROOT)/build/server/plugins/postgresql/test + + + +all: $(TESTDB) + +$(PGTEST): + mkdir -p $(PGTEST) diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/test/createtestdb.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/test/createtestdb.sh Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,66 @@ +#!/bin/sh + +BUILD_ROOT=../../../../.. + +PGTEST=$BUILD_ROOT/build/server/plugins/postgresql/test +TESTDB=$PGTEST/data + +echo "## prepare testdb directory" +rm -Rf $TESTDB +mkdir $TESTDB +if [ $? -ne 0 ]; then + exit 1 +fi + +TESTDB_ABS=`realpath $TESTDB` +echo "testdb path: " $TESTDB_ABS + +echo "## init testdb" +initdb -D $TESTDB_ABS +if [ $? -ne 0 ]; then + exit 1 +fi + +cp pg/postgresql.conf $TESTDB +mkdir $TESTDB/run + +echo "## start database" +pg_ctl -D $TESTDB_ABS start +if [ $? -ne 0 ]; then + echo "## start failed" + exit 1 +fi + +echo "## create testdb" +createdb -h $TESTDB_ABS/run testdb +if [ $? -ne 0 ]; then + echo "## createdb failed" + echo "## stop database" + pg_ctl -D $TESTDB_ABS stop + exit 1 +fi +echo "success" + +echo "## create test data" +psql -h $TESTDB_ABS/run -d testdb -f postgresql_vfs.sql +if [ $? -ne 0 ]; then + echo "## create test data (1) failed" + echo "## stop database" + pg_ctl -D $TESTDB_ABS stop + exit 1 +fi +psql -h $TESTDB_ABS/run -d testdb -f postgresql_vfs_testdata.sql +if [ $? -ne 0 ]; then + echo "## create test data (2) failed" + echo "## stop database" + pg_ctl -D $TESTDB_ABS stop + exit 1 +fi +echo "success" + +echo "## stop database" +pg_ctl -D $TESTDB_ABS stop +exit $? + + + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/test/pg/postgresql.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/test/pg/postgresql.conf Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,805 @@ +# ----------------------------- +# PostgreSQL configuration file +# ----------------------------- +# +# This file consists of lines of the form: +# +# name = value +# +# (The "=" is optional.) Whitespace may be used. Comments are introduced with +# "#" anywhere on a line. The complete list of parameter names and allowed +# values can be found in the PostgreSQL documentation. +# +# The commented-out settings shown in this file represent the default values. +# Re-commenting a setting is NOT sufficient to revert it to the default value; +# you need to reload the server. +# +# This file is read on server startup and when the server receives a SIGHUP +# signal. If you edit the file on a running system, you have to SIGHUP the +# server for the changes to take effect, run "pg_ctl reload", or execute +# "SELECT pg_reload_conf()". Some parameters, which are marked below, +# require a server shutdown and restart to take effect. +# +# Any parameter can also be given as a command-line option to the server, e.g., +# "postgres -c log_connections=on". Some parameters can be changed at run time +# with the "SET" SQL command. +# +# Memory units: B = bytes Time units: us = microseconds +# kB = kilobytes ms = milliseconds +# MB = megabytes s = seconds +# GB = gigabytes min = minutes +# TB = terabytes h = hours +# d = days + + +#------------------------------------------------------------------------------ +# FILE LOCATIONS +#------------------------------------------------------------------------------ + +# The default values of these variables are driven from the -D command-line +# option or PGDATA environment variable, represented here as ConfigDir. + +#data_directory = 'ConfigDir' # use data in another directory + # (change requires restart) +#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file + # (change requires restart) +#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file + # (change requires restart) + +# If external_pid_file is not explicitly set, no extra PID file is written. +#external_pid_file = '' # write an extra PID file + # (change requires restart) + + +#------------------------------------------------------------------------------ +# CONNECTIONS AND AUTHENTICATION +#------------------------------------------------------------------------------ + +# - Connection Settings - + +unix_socket_directories = 'run' + +# Disable TCP for testing +listen_addresses = '' + + +#listen_addresses = 'localhost' # what IP address(es) to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +#port = 5432 # (change requires restart) +max_connections = 100 # (change requires restart) +#superuser_reserved_connections = 3 # (change requires restart) +#unix_socket_directories = '/tmp' # comma-separated list of directories + # (change requires restart) +#unix_socket_group = '' # (change requires restart) +#unix_socket_permissions = 0777 # begin with 0 to use octal notation + # (change requires restart) +#bonjour = off # advertise server via Bonjour + # (change requires restart) +#bonjour_name = '' # defaults to the computer name + # (change requires restart) + +# - TCP settings - +# see "man tcp" for details + +#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; + # 0 selects the system default +#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; + # 0 selects the system default +#tcp_keepalives_count = 0 # TCP_KEEPCNT; + # 0 selects the system default +#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; + # 0 selects the system default + +#client_connection_check_interval = 0 # time between checks for client + # disconnection while running queries; + # 0 for never + +# - Authentication - + +#authentication_timeout = 1min # 1s-600s +#password_encryption = scram-sha-256 # scram-sha-256 or md5 +#db_user_namespace = off + +# GSSAPI using Kerberos +#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' +#krb_caseins_users = off + +# - SSL - + +#ssl = off +#ssl_ca_file = '' +#ssl_cert_file = 'server.crt' +#ssl_crl_file = '' +#ssl_crl_dir = '' +#ssl_key_file = 'server.key' +#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers +#ssl_prefer_server_ciphers = on +#ssl_ecdh_curve = 'prime256v1' +#ssl_min_protocol_version = 'TLSv1.2' +#ssl_max_protocol_version = '' +#ssl_dh_params_file = '' +#ssl_passphrase_command = '' +#ssl_passphrase_command_supports_reload = off + + +#------------------------------------------------------------------------------ +# RESOURCE USAGE (except WAL) +#------------------------------------------------------------------------------ + +# - Memory - + +shared_buffers = 128MB # min 128kB + # (change requires restart) +#huge_pages = try # on, off, or try + # (change requires restart) +#huge_page_size = 0 # zero for system default + # (change requires restart) +#temp_buffers = 8MB # min 800kB +#max_prepared_transactions = 0 # zero disables the feature + # (change requires restart) +# Caution: it is not advisable to set max_prepared_transactions nonzero unless +# you actively intend to use prepared transactions. +#work_mem = 4MB # min 64kB +#hash_mem_multiplier = 1.0 # 1-1000.0 multiplier on hash table work_mem +#maintenance_work_mem = 64MB # min 1MB +#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem +#logical_decoding_work_mem = 64MB # min 64kB +#max_stack_depth = 2MB # min 100kB +#shared_memory_type = mmap # the default is the first option + # supported by the operating system: + # mmap + # sysv + # windows + # (change requires restart) +dynamic_shared_memory_type = posix # the default is the first option + # supported by the operating system: + # posix + # sysv + # windows + # mmap + # (change requires restart) +#min_dynamic_shared_memory = 0MB # (change requires restart) + +# - Disk - + +#temp_file_limit = -1 # limits per-process temp file space + # in kilobytes, or -1 for no limit + +# - Kernel Resources - + +#max_files_per_process = 1000 # min 64 + # (change requires restart) + +# - Cost-Based Vacuum Delay - + +#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) +#vacuum_cost_page_hit = 1 # 0-10000 credits +#vacuum_cost_page_miss = 2 # 0-10000 credits +#vacuum_cost_page_dirty = 20 # 0-10000 credits +#vacuum_cost_limit = 200 # 1-10000 credits + +# - Background Writer - + +#bgwriter_delay = 200ms # 10-10000ms between rounds +#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables +#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round +#bgwriter_flush_after = 0 # measured in pages, 0 disables + +# - Asynchronous Behavior - + +#backend_flush_after = 0 # measured in pages, 0 disables +#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching +#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching +#max_worker_processes = 8 # (change requires restart) +#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers +#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers +#max_parallel_workers = 8 # maximum number of max_worker_processes that + # can be used in parallel operations +#parallel_leader_participation = on +#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate + # (change requires restart) + + +#------------------------------------------------------------------------------ +# WRITE-AHEAD LOG +#------------------------------------------------------------------------------ + +# - Settings - + +#wal_level = replica # minimal, replica, or logical + # (change requires restart) +#fsync = on # flush data to disk for crash safety + # (turning this off can cause + # unrecoverable data corruption) +#synchronous_commit = on # synchronization level; + # off, local, remote_write, remote_apply, or on +#wal_sync_method = fsync # the default is the first option + # supported by the operating system: + # open_datasync + # fdatasync (default on Linux and FreeBSD) + # fsync + # fsync_writethrough + # open_sync +#full_page_writes = on # recover from partial page writes +#wal_log_hints = off # also do full page writes of non-critical updates + # (change requires restart) +#wal_compression = off # enable compression of full-page writes +#wal_init_zero = on # zero-fill new WAL files +#wal_recycle = on # recycle WAL files +#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers + # (change requires restart) +#wal_writer_delay = 200ms # 1-10000 milliseconds +#wal_writer_flush_after = 1MB # measured in pages, 0 disables +#wal_skip_threshold = 2MB + +#commit_delay = 0 # range 0-100000, in microseconds +#commit_siblings = 5 # range 1-1000 + +# - Checkpoints - + +#checkpoint_timeout = 5min # range 30s-1d +#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 +#checkpoint_flush_after = 0 # measured in pages, 0 disables +#checkpoint_warning = 30s # 0 disables +max_wal_size = 1GB +min_wal_size = 80MB + +# - Archiving - + +#archive_mode = off # enables archiving; off, on, or always + # (change requires restart) +#archive_command = '' # command to use to archive a logfile segment + # placeholders: %p = path of file to archive + # %f = file name only + # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' +#archive_timeout = 0 # force a logfile segment switch after this + # number of seconds; 0 disables + +# - Archive Recovery - + +# These are only used in recovery mode. + +#restore_command = '' # command to use to restore an archived logfile segment + # placeholders: %p = path of file to restore + # %f = file name only + # e.g. 'cp /mnt/server/archivedir/%f %p' +#archive_cleanup_command = '' # command to execute at every restartpoint +#recovery_end_command = '' # command to execute at completion of recovery + +# - Recovery Target - + +# Set these only when performing a targeted recovery. + +#recovery_target = '' # 'immediate' to end recovery as soon as a + # consistent state is reached + # (change requires restart) +#recovery_target_name = '' # the named restore point to which recovery will proceed + # (change requires restart) +#recovery_target_time = '' # the time stamp up to which recovery will proceed + # (change requires restart) +#recovery_target_xid = '' # the transaction ID up to which recovery will proceed + # (change requires restart) +#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed + # (change requires restart) +#recovery_target_inclusive = on # Specifies whether to stop: + # just after the specified recovery target (on) + # just before the recovery target (off) + # (change requires restart) +#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID + # (change requires restart) +#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' + # (change requires restart) + + +#------------------------------------------------------------------------------ +# REPLICATION +#------------------------------------------------------------------------------ + +# - Sending Servers - + +# Set these on the primary and on any standby that will send replication data. + +#max_wal_senders = 10 # max number of walsender processes + # (change requires restart) +#max_replication_slots = 10 # max number of replication slots + # (change requires restart) +#wal_keep_size = 0 # in megabytes; 0 disables +#max_slot_wal_keep_size = -1 # in megabytes; -1 disables +#wal_sender_timeout = 60s # in milliseconds; 0 disables +#track_commit_timestamp = off # collect timestamp of transaction commit + # (change requires restart) + +# - Primary Server - + +# These settings are ignored on a standby server. + +#synchronous_standby_names = '' # standby servers that provide sync rep + # method to choose sync standbys, number of sync standbys, + # and comma-separated list of application_name + # from standby(s); '*' = all +#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed + +# - Standby Servers - + +# These settings are ignored on a primary server. + +#primary_conninfo = '' # connection string to sending server +#primary_slot_name = '' # replication slot on sending server +#promote_trigger_file = '' # file name whose presence ends recovery +#hot_standby = on # "off" disallows queries during recovery + # (change requires restart) +#max_standby_archive_delay = 30s # max delay before canceling queries + # when reading WAL from archive; + # -1 allows indefinite delay +#max_standby_streaming_delay = 30s # max delay before canceling queries + # when reading streaming WAL; + # -1 allows indefinite delay +#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name + # is not set +#wal_receiver_status_interval = 10s # send replies at least this often + # 0 disables +#hot_standby_feedback = off # send info from standby to prevent + # query conflicts +#wal_receiver_timeout = 60s # time that receiver waits for + # communication from primary + # in milliseconds; 0 disables +#wal_retrieve_retry_interval = 5s # time to wait before retrying to + # retrieve WAL after a failed attempt +#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery + +# - Subscribers - + +# These settings are ignored on a publisher. + +#max_logical_replication_workers = 4 # taken from max_worker_processes + # (change requires restart) +#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers + + +#------------------------------------------------------------------------------ +# QUERY TUNING +#------------------------------------------------------------------------------ + +# - Planner Method Configuration - + +#enable_async_append = on +#enable_bitmapscan = on +#enable_gathermerge = on +#enable_hashagg = on +#enable_hashjoin = on +#enable_incremental_sort = on +#enable_indexscan = on +#enable_indexonlyscan = on +#enable_material = on +#enable_memoize = on +#enable_mergejoin = on +#enable_nestloop = on +#enable_parallel_append = on +#enable_parallel_hash = on +#enable_partition_pruning = on +#enable_partitionwise_join = off +#enable_partitionwise_aggregate = off +#enable_seqscan = on +#enable_sort = on +#enable_tidscan = on + +# - Planner Cost Constants - + +#seq_page_cost = 1.0 # measured on an arbitrary scale +#random_page_cost = 4.0 # same scale as above +#cpu_tuple_cost = 0.01 # same scale as above +#cpu_index_tuple_cost = 0.005 # same scale as above +#cpu_operator_cost = 0.0025 # same scale as above +#parallel_setup_cost = 1000.0 # same scale as above +#parallel_tuple_cost = 0.1 # same scale as above +#min_parallel_table_scan_size = 8MB +#min_parallel_index_scan_size = 512kB +#effective_cache_size = 4GB + +#jit_above_cost = 100000 # perform JIT compilation if available + # and query more expensive than this; + # -1 disables +#jit_inline_above_cost = 500000 # inline small functions if query is + # more expensive than this; -1 disables +#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if + # query is more expensive than this; + # -1 disables + +# - Genetic Query Optimizer - + +#geqo = on +#geqo_threshold = 12 +#geqo_effort = 5 # range 1-10 +#geqo_pool_size = 0 # selects default based on effort +#geqo_generations = 0 # selects default based on effort +#geqo_selection_bias = 2.0 # range 1.5-2.0 +#geqo_seed = 0.0 # range 0.0-1.0 + +# - Other Planner Options - + +#default_statistics_target = 100 # range 1-10000 +#constraint_exclusion = partition # on, off, or partition +#cursor_tuple_fraction = 0.1 # range 0.0-1.0 +#from_collapse_limit = 8 +#jit = on # allow JIT compilation +#join_collapse_limit = 8 # 1 disables collapsing of explicit + # JOIN clauses +#plan_cache_mode = auto # auto, force_generic_plan or + # force_custom_plan + + +#------------------------------------------------------------------------------ +# REPORTING AND LOGGING +#------------------------------------------------------------------------------ + +# - Where to Log - + +log_destination = 'syslog' +#log_destination = 'stderr' # Valid values are combinations of + # stderr, csvlog, syslog, and eventlog, + # depending on platform. csvlog + # requires logging_collector to be on. + +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr and csvlog + # into log files. Required to be on for + # csvlogs. + # (change requires restart) + +# These are only used if logging_collector is on: +#log_directory = 'log' # directory where log files are written, + # can be absolute or relative to PGDATA +#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, + # can include strftime() escapes +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that time. 0 disables. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much log output. + # 0 disables. +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +# These are relevant when logging to syslog: +#syslog_facility = 'LOCAL0' +#syslog_ident = 'postgres' +#syslog_sequence_numbers = on +#syslog_split_messages = on + +# This is only relevant when logging to eventlog (Windows): +# (change requires restart) +#event_source = 'PostgreSQL' + +# - When to Log - + +#log_min_messages = warning # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic + +#log_min_error_statement = error # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic (effectively off) + +#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements + # and their durations, > 0 logs only + # statements running at least this number + # of milliseconds + +#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements + # and their durations, > 0 logs only a sample of + # statements running at least this number + # of milliseconds; + # sample fraction is determined by log_statement_sample_rate + +#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding + # log_min_duration_sample to be logged; + # 1.0 logs all such statements, 0.0 never logs + + +#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements + # are logged regardless of their duration; 1.0 logs all + # statements from all transactions, 0.0 never logs + +# - What to Log - + +#debug_print_parse = off +#debug_print_rewritten = off +#debug_print_plan = off +#debug_pretty_print = on +#log_autovacuum_min_duration = -1 # log autovacuum activity; + # -1 disables, 0 logs all actions and + # their durations, > 0 logs only + # actions running at least this number + # of milliseconds. +#log_checkpoints = off +#log_connections = off +#log_disconnections = off +#log_duration = off +#log_error_verbosity = default # terse, default, or verbose messages +#log_hostname = off +#log_line_prefix = '%m [%p] ' # special values: + # %a = application name + # %u = user name + # %d = database name + # %r = remote host and port + # %h = remote host + # %b = backend type + # %p = process ID + # %P = process ID of parallel group leader + # %t = timestamp without milliseconds + # %m = timestamp with milliseconds + # %n = timestamp with milliseconds (as a Unix epoch) + # %Q = query ID (0 if none or not computed) + # %i = command tag + # %e = SQL state + # %c = session ID + # %l = session line number + # %s = session start timestamp + # %v = virtual transaction ID + # %x = transaction ID (0 if none) + # %q = stop here in non-session + # processes + # %% = '%' + # e.g. '<%u%%%d> ' +#log_lock_waits = off # log lock waits >= deadlock_timeout +#log_recovery_conflict_waits = off # log standby recovery conflict waits + # >= deadlock_timeout +#log_parameter_max_length = -1 # when logging statements, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_parameter_max_length_on_error = 0 # when logging an error, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_statement = 'none' # none, ddl, mod, all +#log_replication_commands = off +#log_temp_files = -1 # log temporary files equal or larger + # than the specified size in kilobytes; + # -1 disables, 0 logs all temp files +log_timezone = 'Europe/Berlin' + + +#------------------------------------------------------------------------------ +# PROCESS TITLE +#------------------------------------------------------------------------------ + +#cluster_name = '' # added to process titles if nonempty + # (change requires restart) + +# On FreeBSD, this is a performance hog, so keep it off if you need speed +update_process_title = off + + +#------------------------------------------------------------------------------ +# STATISTICS +#------------------------------------------------------------------------------ + +# - Query and Index Statistics Collector - + +#track_activities = on +#track_activity_query_size = 1024 # (change requires restart) +#track_counts = on +#track_io_timing = off +#track_wal_io_timing = off +#track_functions = none # none, pl, all +#stats_temp_directory = 'pg_stat_tmp' + + +# - Monitoring - + +#compute_query_id = auto +#log_statement_stats = off +#log_parser_stats = off +#log_planner_stats = off +#log_executor_stats = off + + +#------------------------------------------------------------------------------ +# AUTOVACUUM +#------------------------------------------------------------------------------ + +#autovacuum = on # Enable autovacuum subprocess? 'on' + # requires track_counts to also be on. +#autovacuum_max_workers = 3 # max number of autovacuum subprocesses + # (change requires restart) +#autovacuum_naptime = 1min # time between autovacuum runs +#autovacuum_vacuum_threshold = 50 # min number of row updates before + # vacuum +#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts + # before vacuum; -1 disables insert + # vacuums +#autovacuum_analyze_threshold = 50 # min number of row updates before + # analyze +#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum +#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table + # size before insert vacuum +#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze +#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum + # (change requires restart) +#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age + # before forced vacuum + # (change requires restart) +#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for + # autovacuum, in milliseconds; + # -1 means use vacuum_cost_delay +#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for + # autovacuum, -1 means use + # vacuum_cost_limit + + +#------------------------------------------------------------------------------ +# CLIENT CONNECTION DEFAULTS +#------------------------------------------------------------------------------ + +# - Statement Behavior - + +#client_min_messages = notice # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # log + # notice + # warning + # error +#search_path = '"$user", public' # schema names +#row_security = on +#default_table_access_method = 'heap' +#default_tablespace = '' # a tablespace name, '' uses the default +#default_toast_compression = 'pglz' # 'pglz' or 'lz4' +#temp_tablespaces = '' # a list of tablespace names, '' uses + # only default tablespace +#check_function_bodies = on +#default_transaction_isolation = 'read committed' +#default_transaction_read_only = off +#default_transaction_deferrable = off +#session_replication_role = 'origin' +#statement_timeout = 0 # in milliseconds, 0 is disabled +#lock_timeout = 0 # in milliseconds, 0 is disabled +#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled +#idle_session_timeout = 0 # in milliseconds, 0 is disabled +#vacuum_freeze_table_age = 150000000 +#vacuum_freeze_min_age = 50000000 +#vacuum_failsafe_age = 1600000000 +#vacuum_multixact_freeze_table_age = 150000000 +#vacuum_multixact_freeze_min_age = 5000000 +#vacuum_multixact_failsafe_age = 1600000000 +#bytea_output = 'hex' # hex, escape +#xmlbinary = 'base64' +#xmloption = 'content' +#gin_pending_list_limit = 4MB + +# - Locale and Formatting - + +datestyle = 'iso, dmy' +#intervalstyle = 'postgres' +timezone = 'Europe/Berlin' +#timezone_abbreviations = 'Default' # Select the set of available time zone + # abbreviations. Currently, there are + # Default + # Australia (historical usage) + # India + # You can create your own file in + # share/timezonesets/. +#extra_float_digits = 1 # min -15, max 3; any value >0 actually + # selects precise output mode +#client_encoding = sql_ascii # actually, defaults to database + # encoding + +# These settings are initialized by initdb, but they can be changed. +lc_messages = 'de_DE.UTF-8' # locale for system error message + # strings +lc_monetary = 'de_DE.UTF-8' # locale for monetary formatting +lc_numeric = 'de_DE.UTF-8' # locale for number formatting +lc_time = 'de_DE.UTF-8' # locale for time formatting + +# default configuration for text search +default_text_search_config = 'pg_catalog.german' + +# - Shared Library Preloading - + +#local_preload_libraries = '' +#session_preload_libraries = '' +#shared_preload_libraries = '' # (change requires restart) +#jit_provider = 'llvmjit' # JIT library to use + +# - Other Defaults - + +#dynamic_library_path = '$libdir' +#gin_fuzzy_search_limit = 0 + + +#------------------------------------------------------------------------------ +# LOCK MANAGEMENT +#------------------------------------------------------------------------------ + +#deadlock_timeout = 1s +#max_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_relation = -2 # negative values mean + # (max_pred_locks_per_transaction + # / -max_pred_locks_per_relation) - 1 +#max_pred_locks_per_page = 2 # min 0 + + +#------------------------------------------------------------------------------ +# VERSION AND PLATFORM COMPATIBILITY +#------------------------------------------------------------------------------ + +# - Previous PostgreSQL Versions - + +#array_nulls = on +#backslash_quote = safe_encoding # on, off, or safe_encoding +#escape_string_warning = on +#lo_compat_privileges = off +#quote_all_identifiers = off +#standard_conforming_strings = on +#synchronize_seqscans = on + +# - Other Platforms and Clients - + +#transform_null_equals = off + + +#------------------------------------------------------------------------------ +# ERROR HANDLING +#------------------------------------------------------------------------------ + +#exit_on_error = off # terminate session on any error? +#restart_after_crash = on # reinitialize after backend crash? +#data_sync_retry = off # retry or panic on failure to fsync + # data? + # (change requires restart) +#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) + + +#------------------------------------------------------------------------------ +# CONFIG FILE INCLUDES +#------------------------------------------------------------------------------ + +# These options allow settings to be loaded from files other than the +# default postgresql.conf. Note that these are directives, not variable +# assignments, so they can usefully be given more than once. + +#include_dir = '...' # include files ending in '.conf' from + # a directory, e.g., 'conf.d' +#include_if_exists = '...' # include file only if it exists +#include = '...' # include file + + +#------------------------------------------------------------------------------ +# CUSTOMIZED OPTIONS +#------------------------------------------------------------------------------ + +# Add settings for extensions here diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/test/postgresql_vfs.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/test/postgresql_vfs.sql Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,16 @@ + +create table Resource ( + resource_id serial primary key, + parent_id int references Resource(resource_id), + nodename text not null, + iscollection boolean not null default false, + + lastmodified timestamp not null default current_date, + creationdate timestamp not null default current_date, + contentlength bigint not null default 0, + + resoid oid, + + unique(parent_id, nodename) +); + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/test/postgresql_vfs_testdata.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/test/postgresql_vfs_testdata.sql Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,29 @@ + +do $$ +declare + res_id int; +begin + insert into Resource (nodename, iscollection) values ('', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, resoid) values + (res_id, 'file1.txt', (select lo_create(0))), + (res_id, 'file2.txt', (select lo_create(0))), + (res_id, 'file3.txt', (select lo_create(0))), + (res_id, 'file4.txt', (select lo_create(0))); + + insert into Resource(parent_id, nodename, iscollection) values + (res_id, 'dir1', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, iscollection) values + (res_id, 'dir2', true); + res_id := lastval(); + + insert into Resource(parent_id, nodename, resoid) values + (res_id, 'd1file1.txt', (select lo_create(0))), + (res_id, 'd2file1.txt', (select lo_create(0))), + (res_id, 'd2file2.txt', (select lo_create(0))); + +end $$; + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/vfs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/vfs.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,1013 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "vfs.h" +#include "config.h" + +#include + +#include "../../util/util.h" +#include "../../util/pblock.h" + +static VFS pg_vfs_class = { + pg_vfs_open, + pg_vfs_stat, + pg_vfs_fstat, + pg_vfs_opendir, + pg_vfs_fdopendir, + pg_vfs_mkdir, + pg_vfs_unlink, + pg_vfs_rmdir +}; + +static VFS_IO pg_vfs_io_class = { + pg_vfs_io_read, + pg_vfs_io_write, + pg_vfs_io_pread, + pg_vfs_io_pwrite, + pg_vfs_io_seek, + pg_vfs_io_close, + NULL, // no pg aio implementation yet + NULL, + pg_vfs_io_getetag +}; + +static VFS_DIRIO pg_vfs_dirio_class = { + pg_vfs_dirio_readdir, + pg_vfs_dirio_close +}; + + +/* + * SQL Queries + */ + +// Resolves a path into resource_id and parent_id +// params: $1: path string +static const char *sql_resolve_path = + "with recursive resolvepath as (\n\ + select\n\ + resource_id,\n\ + parent_id,\n\ + '' as fullpath,\n\ + resoid,\n\ + iscollection,\n\ + lastmodified,\n\ + creationdate,\n\ + contentlength,\n\ + etag,\n\ + regexp_split_to_array($1, '/') as pathelm,\n\ + 1 as pathdepth\n\ + from Resource\n\ + where resource_id = $2\n\ + union\n\ + select\n\ + r.resource_id,\n\ + r.parent_id,\n\ + p.fullpath || '/' || r.nodename,\n\ + r.resoid,\n\ + r.iscollection,\n\ + r.lastmodified,\n\ + r.creationdate,\n\ + r.contentlength,\n\ + r.etag,\n\ + p.pathelm,\n\ + p.pathdepth + 1\n\ + from Resource r\n\ + inner join resolvepath p on r.parent_id = p.resource_id\n\ + where p.pathelm[p.pathdepth+1] = r.nodename\n\ + )\n\ + select resource_id, parent_id, fullpath, resoid, iscollection, lastmodified, creationdate, contentlength, etag from resolvepath\n\ + where fullpath = $1 ;"; + +// Same as sql_resolve_path, but it returns the root collection +// params: $1: path string (should be '/') +static const char *sql_get_root = "select resource_id, parent_id, $1 as fullpath, resoid, true as iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $2;"; + +// Get all children of a specific collection +// params: $1: parent resource_id +static const char *sql_get_children = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where parent_id = $1;"; + +// Get resource +// params: $1: resource_id +static const char *sql_get_resource = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $1;"; + +// Create resource +// params: $1: parent_id +// $2: node name +static const char *sql_create_resource = + "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength, resoid) values\n\ + ($1, $2, false, now(), now(), 0, lo_creat(-1))\n\ + returning resource_id, resoid, lastmodified, creationdate;"; + +// Create collection +// params: $1: parent_id +// $2: node name +static const char *sql_create_collection = + "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength) values\n\ + ($1, $2, true, now(), now(), 0)\n\ + returning resource_id, lastmodified, creationdate;"; + +// Update resource metadata +// params: $1: resource_id +// $2: contentlength +static const char *sql_update_resource = "update Resource set contentlength = $2, lastmodified = now(), etag = gen_random_uuid() where resource_id = $1;"; + +// Delete a resource +// params: $1: resource_id +static const char *sql_delete_res = "delete from Resource where parent_id is not null and resource_id = $1;"; + + +void* pg_vfs_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { + return pg_init_repo(cfg, pool, config); +} + +VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb, void *initData) { + PgRepository *repo = initData; + + char *resource_pool; + if(repo) { + resource_pool = repo->resourcepool.ptr; + } else { + // resourcepool is required + resource_pool = pblock_findval("resourcepool", pb); + if(!resource_pool) { + log_ereport(LOG_MISCONFIG, "pg_vfs_create: missing resourcepool parameter"); + return NULL; + } + } + + // get the resource first (most likely to fail due to misconfig) + ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0); + if(!resdata) { + log_ereport(LOG_MISCONFIG, "postgresql vfs: resource pool %s not found", resource_pool); + return NULL; + } + // resdata will be freed automatically when the request is finished + + return pg_vfs_create_from_resourcedata(sn, rq, repo, resdata); +} + +VFS* pg_vfs_create_from_resourcedata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) { + // Create a new VFS object and a separate instance object + // VFS contains fptrs that can be copied from pg_vfs_class + // instance contains request specific data (PGconn) + VFS *vfs = pool_malloc(sn->pool, sizeof(VFS)); + if(!vfs) { + return NULL; + } + + PgVFS *vfs_priv = pool_malloc(sn->pool, sizeof(PgVFS)); + if(!vfs_priv) { + pool_free(sn->pool, vfs); + return NULL; + } + vfs_priv->connection = resdata->data; + vfs_priv->pg_resource = resdata; + vfs_priv->root_resource_id = repo->root_resource_id; + snprintf(vfs_priv->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id); + + memcpy(vfs, &pg_vfs_class, sizeof(VFS)); + vfs->flags = 0; + vfs->instance = vfs_priv; + + return vfs; +} + + +int pg_resolve_path( + PGconn *connection, + const char *path, + const char *root_id, + int64_t *parent_id, + int64_t *resource_id, + Oid *oid, + const char **resource_name, + WSBool *iscollection, + struct stat *s, + char *etag, + int *res_errno) +{ + // basic path validation + if(!path) return 1; + size_t pathlen = strlen(path); + if(pathlen == 0) return 1; + if(path[0] != '/') { + return 1; + } + + char *pathf = NULL; + if(pathlen > 1 && path[pathlen-1] == '/') { + pathf = malloc(pathlen); + memcpy(pathf, path, pathlen); + pathf[pathlen-1] = 0; // remove trailing '/' + path = pathf; + } + + // get last node of path + *resource_name = util_resource_name(path); + + const char *sql = pathlen == 1 ? sql_get_root : sql_resolve_path; + const char* params[2] = { path, root_id }; + PGresult *result = PQexecParams( + connection, + sql, + 2, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(pathf) { + free(pathf); + } + + if(!result) return 1; + + int ret = 1; + //int nfields = PQnfields(result); + int nrows = PQntuples(result); + if(nrows == 1) { + char *resource_id_str = PQgetvalue(result, 0, 0); + char *parent_id_str = PQgetvalue(result, 0, 1); + char *iscol = PQgetvalue(result, 0, 4); + char *lastmodified = PQgetvalue(result, 0, 5); + char *creationdate = PQgetvalue(result, 0, 6); + char *contentlength = PQgetvalue(result, 0, 7); + char *res_etag = PQgetvalue(result, 0, 8); + if(resource_id_str && parent_id_str) { + if(util_strtoint(resource_id_str, resource_id)) { + ret = 0; // success + } + // optionally get parent_id + util_strtoint(parent_id_str, parent_id); + } + + if(oid) { + char *resoid = PQgetvalue(result, 0, 3); + int64_t roid; + if(resoid && util_strtoint(resoid, &roid)) { + *oid = roid; + } + } + + if(iscollection && iscol) { + *iscollection = iscol[0] == 't' ? TRUE : FALSE; + } + + if(s) { + pg_set_stat(s, iscol, lastmodified, creationdate, contentlength); + } + + if(etag) { + size_t etag_len = strlen(res_etag); + if(etag_len < PG_ETAG_MAXLEN) + memcpy(etag, res_etag, etag_len+1); + } + } else if(res_errno) { + *res_errno = ENOENT; + } + + PQclear(result); + + return ret; +} + + +void pg_set_stat( + struct stat *s, + const char *iscollection, + const char *lastmodified, + const char *creationdate, + const char *contentlength) +{ + memset(s, 0, sizeof(struct stat)); + if(iscollection) { + WSBool iscol = iscollection[0] == 't' ? TRUE : FALSE; + if(iscol) { + s->st_mode |= 0x4000; + } + } + s->st_mtime = pg_convert_timestamp(lastmodified); + + if(contentlength) { + int64_t len; + if(util_strtoint(contentlength, &len)) { + s->st_size = len; + } + } +} + +static int pg_create_res( + PgVFS *pg, + const char *resparentid_str, + const char *nodename, + int64_t *new_resource_id, + Oid *oid, + const char **resource_name, + struct stat *s) +{ + const char* params[2] = { resparentid_str, nodename }; + PGresult *result = PQexecParams( + pg->connection, + sql_create_resource, + 2, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(!result) return 1; + + int ret = 1; + if(PQntuples(result) == 1) { + // sql insert succesful + ret = 0; + + char *id_str = PQgetvalue(result, 0, 0); + char *oid_str = PQgetvalue(result, 0, 1); + char *lastmodified = PQgetvalue(result, 0, 2); + char *creationdate = PQgetvalue(result, 0, 3); + + if(new_resource_id) { + if(!id_str || !util_strtoint(id_str, new_resource_id)) { + ret = 1; // shouldn't happen + log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert resource_id to int"); + } + } + if(oid) { + int64_t i; + if(!oid_str || !util_strtoint(oid_str, &i)) { + ret = 1; // shouldn't happen + log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert oid to int"); + } else { + *oid = i; + } + } + if(resource_name) { + *resource_name = nodename; + } + if(s) { + pg_set_stat(s, 0, lastmodified, creationdate, NULL); + } + } + + PQclear(result); + + return ret; +} + + +static int pg_create_col( + PgVFS *pg, + const char *resparentid_str, + const char *nodename, + int64_t *new_resource_id, + const char **resource_name, + struct stat *s) +{ + const char* params[2] = { resparentid_str, nodename }; + PGresult *result = PQexecParams( + pg->connection, + sql_create_collection, + 2, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(!result) return 1; + + int ret = 1; + if(PQntuples(result) == 1) { + // sql insert succesful + ret = 0; + + char *id_str = PQgetvalue(result, 0, 0); + char *lastmodified = PQgetvalue(result, 0, 1); + char *creationdate = PQgetvalue(result, 0, 2); + + if(new_resource_id) { + if(!id_str || !util_strtoint(id_str, new_resource_id)) { + ret = 1; // shouldn't happen + log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_collection: Could not convert resource_id to int"); + } + } + if(resource_name) { + *resource_name = nodename; + } + if(s) { + pg_set_stat(s, 0, lastmodified, creationdate, NULL); + } + } + + PQclear(result); + + return ret; +} + +int pg_create_file( + VFSContext *ctx, + PgVFS *pg, + const char *path, + int64_t *new_resource_id, + int64_t *res_parent_id, + Oid *oid, + const char **resource_name, + struct stat *s, + WSBool collection) +{ + char *parent_path = util_parent_path(path); + if(!parent_path) return 1; + + size_t pathlen = strlen(path); + char *pathf = NULL; + if(pathlen > 1 && path[pathlen-1] == '/') { + pathf = malloc(pathlen); + memcpy(pathf, path, pathlen); + pathf[pathlen-1] = 0; // remove trailing '/' + path = pathf; + } + + const char *nodename = util_resource_name(path); + + // resolve the parent path + // if the parent path can't be resolved, we are done + const char *resname; + int64_t resource_id, parent_id; + resource_id = -1; + parent_id = -1; + WSBool iscollection; + Oid unused_oid = 0; + + int err = pg_resolve_path( + pg->connection, + parent_path, + pg->root_resource_id_str, + &parent_id, + &resource_id, + &unused_oid, + &resname, + &iscollection, + NULL, + NULL, + &ctx->vfs_errno); + FREE(parent_path); + if(err) { + ctx->vfs_errno = ENOENT; + if(pathf) free(pathf); + return 1; + } + + // parent path exists, check if it is a collection + if(!iscollection) { + if(pathf) free(pathf); + return 1; + } + + // create new Resource + char resid_str[32]; + snprintf(resid_str, 32, "%" PRId64, resource_id); // convert parent resource_id to string + + int ret; + if(collection) { + ret = pg_create_col(pg, resid_str, nodename, new_resource_id, resource_name, s); + } else { + ret = pg_create_res(pg, resid_str, nodename, new_resource_id, oid, resource_name, s); + } + + if(pathf) free(pathf); + + if(res_parent_id) { + // resource_id is still the id of the parent from previous pg_resolve_path call + *res_parent_id = resource_id; + } + + return ret; +} + +int pg_remove_res( + VFSContext *ctx, + PgVFS *pg, + int64_t resource_id, + Oid oid) +{ + // create transaction savepoint + PGresult *result = PQexec(pg->connection, "savepoint del_res;"); + ExecStatusType execStatus = PQresultStatus(result); + PQclear(result); + if(execStatus != PGRES_COMMAND_OK) { + return 1; + } + + if(oid > 0) { + if(lo_unlink(pg->connection, oid) != 1) { + // restore savepoint + result = PQexec(pg->connection, "rollback to savepoint del_res;"); + PQclear(result); + return 1; // error + } + } + + char resid_str[32]; + snprintf(resid_str, 32, "%" PRId64, resource_id); + + const char* params[1] = { resid_str }; + result = PQexecParams( + pg->connection, + sql_delete_res, + 1, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + execStatus = PQresultStatus(result); + PQclear(result); + int ret = 0; + + if(execStatus != PGRES_COMMAND_OK) { + ret = 1; + // restore savepoint + result = PQexec(pg->connection, "rollback to savepoint del_res;"); + PQclear(result); + } else { + // we don't need the savepoint anymore + result = PQexec(pg->connection, "release savepoint del_res;"); + PQclear(result); + } + + return ret; +} + +int pg_update_resource(PgVFS *pg, int64_t resource_id, int64_t contentlength) { + char resid_str[32]; + char ctlen_str[32]; + snprintf(resid_str, 32, "%" PRId64, resource_id); + snprintf(ctlen_str, 32, "%" PRId64, contentlength); + + const char* params[2] = { resid_str, ctlen_str }; + PGresult *result = PQexecParams( + pg->connection, + sql_update_resource, + 2, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + int ret = PQresultStatus(result) == PGRES_COMMAND_OK ? 0 : 1; + PQclear(result); + return ret; +} + +/* -------------------------- VFS functions -------------------------- */ + +SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags) { + VFS *vfs = ctx->vfs; + PgVFS *pg = vfs->instance; + + const char *resname; + int64_t resource_id, parent_id; + resource_id = -1; + parent_id = -1; + WSBool iscollection; + struct stat s; + char etag[PG_ETAG_MAXLEN]; + Oid oid = 0; + if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, etag, &ctx->vfs_errno)) { + if((oflags & O_CREAT) == O_CREAT) { + if(pg_create_file(ctx, pg, path, &resource_id, &parent_id, &oid, &resname, &s, FALSE)) { + return NULL; + } + iscollection = 0; + } else { + return NULL; + } + } + + // store the resource_id in rq->vars + if(ctx->rq) { + char *rq_path = pblock_findkeyval(pb_key_path, ctx->rq->vars); + if(rq_path && !strcmp(rq_path, path)) { + char *res_id_str = pblock_findval("resource_id", ctx->rq->vars); + if(!res_id_str) { + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, resource_id); + pblock_nvinsert("resource_id",resource_id_str, ctx->rq->vars); + } + } + } + + VFSFile *file = pool_malloc(ctx->pool, sizeof(VFSFile)); + if(!file) { + return NULL; + } + PgFile *pgfile = pool_malloc(ctx->pool, sizeof(PgFile)); + if(!pgfile) { + pool_free(ctx->pool, file); + return NULL; + } + + int fd = -1; + if(!iscollection) { + if (PQstatus(pg->connection) != CONNECTION_OK) { + fd = -2; + } + + int lo_mode = INV_READ; + if((oflags & O_RDWR) == O_RDWR) { + lo_mode = INV_READ|INV_WRITE; + } else if((oflags & O_WRONLY) == O_WRONLY) { + lo_mode = INV_WRITE; + } + fd = lo_open(pg->connection, oid, lo_mode); + int err = 0; + if(fd < 0) { + err = 1; + } else if((oflags & O_TRUNC) == O_TRUNC) { + if(lo_truncate(pg->connection, fd, 0)) { + lo_close(pg->connection, fd); + err = 1; + } + } + + if(err) { + pool_free(ctx->pool, file); + pool_free(ctx->pool, pgfile); + return NULL; + } + } + + pgfile->iscollection = iscollection; + pgfile->resource_id = resource_id; + pgfile->parent_id = parent_id; + pgfile->oid = oid; + pgfile->fd = fd; + pgfile->oflags = oflags; + pgfile->s = s; + memcpy(pgfile->etag, etag, PG_ETAG_MAXLEN); + + file->ctx = ctx; + file->io = iscollection ? &pg_vfs_io_class : &pg_vfs_io_class; + file->fd = -1; + file->data = pgfile; + + return file; +} + +int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { + VFS *vfs = ctx->vfs; + PgVFS *pg = vfs->instance; + + int64_t parent_id, resource_id; + const char *resname; + WSBool iscollection; + return pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, buf, NULL, &ctx->vfs_errno); +} + +int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { + PgFile *pgfile = fd->data; + memcpy(buf, &pgfile->s, sizeof(struct stat)); + return 0; +} + +VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path) { + VFSFile *file = pg_vfs_open(ctx, path, O_RDONLY); + if(!file) return NULL; + return pg_vfs_fdopendir(ctx, file); +} + +VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + PgFile *pg = fd->data; + if(!pg->iscollection) { + ctx->vfs_errno = ENOTDIR; + return NULL; + } + + VFSDir *dir = pool_malloc(ctx->pool, sizeof(VFSDir)); + if(!dir) { + fd->io->close(fd); + ctx->vfs_errno = ENOMEM; + return NULL; + } + + PgDir *pgdir = pool_malloc(ctx->pool, sizeof(PgDir)); + if(!pgdir) { + fd->io->close(fd); + pool_free(ctx->pool, dir); + ctx->vfs_errno = ENOMEM; + return NULL; + } + memset(pgdir, 0, sizeof(PgDir)); + pgdir->file = fd; + + dir->ctx = ctx; + dir->io = &pg_vfs_dirio_class; + dir->data = pgdir; + dir->fd = -1; + + return dir; +} + +int pg_vfs_mkdir(VFSContext *ctx, const char *path) { + VFS *vfs = ctx->vfs; + PgVFS *pg = vfs->instance; + + const char *resname; + int64_t resource_id, parent_id; + resource_id = -1; + parent_id = -1; + WSBool iscollection; + struct stat s; + Oid oid = 0; + if(!pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, NULL, &ctx->vfs_errno)) { + ctx->vfs_errno = EEXIST; + return 1; + } + + if(pg_create_file(ctx, pg, path, NULL, NULL, NULL, NULL, NULL, TRUE)) { + return 1; + } + + return 0; +} + +int pg_vfs_unlink(VFSContext *ctx, const char *path) { + VFS *vfs = ctx->vfs; + PgVFS *pg = vfs->instance; + + const char *resname; + int64_t resource_id, parent_id; + resource_id = -1; + parent_id = -1; + WSBool iscollection; + Oid oid = 0; + if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) { + return 1; + } + + if(iscollection) { + ctx->vfs_errno = EISDIR; + return 1; + } + + return pg_remove_res(ctx, pg, resource_id, oid); +} + +int pg_vfs_rmdir(VFSContext *ctx, const char *path) { + VFS *vfs = ctx->vfs; + PgVFS *pg = vfs->instance; + + const char *resname; + int64_t resource_id, parent_id; + resource_id = -1; + parent_id = -1; + WSBool iscollection; + if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) { + return 1; + } + + if(!iscollection) { + ctx->vfs_errno = ENOTDIR; + return 1; + } + + return pg_remove_res(ctx, pg, resource_id, 0); +} + + +/* -------------------------- VFS_IO functions -------------------------- */ + +ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + return lo_read(pgvfs->connection, pg->fd, buf, nbyte); +} + +ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + return lo_write(pgvfs->connection, pg->fd, buf, nbyte); +} + +ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) { + return -1; + } + return lo_read(pgvfs->connection, pg->fd, buf, nbyte); +} + +ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) { + return -1; + } + return lo_write(pgvfs->connection, pg->fd, buf, nbyte); +} + +off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + return lo_lseek64(pgvfs->connection, pg->fd, offset, whence); +} + +off_t pg_vfs_io_tell(SYS_FILE fd) { + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + if(pg->fd < 0) return-1; + return lo_tell64(pgvfs->connection, pg->fd); +} + +void pg_vfs_io_close(SYS_FILE fd) { + pool_handle_t *pool = fd->ctx->pool; + PgVFS *pgvfs = fd->ctx->vfs->instance; + PgFile *pg = fd->data; + + if(pg->fd >= 0) { + if((pg->oflags & (O_WRONLY|O_RDWR)) > 0) { + // file modified, update length and lastmodified + off_t len = pg_vfs_io_seek(fd, 0, SEEK_END); + if(len < 0) len = 0; + + pg_update_resource(pgvfs, pg->resource_id, len); + } + + PgVFS *pgvfs = fd->ctx->vfs->instance; + lo_close(pgvfs->connection, pg->fd); + } + + pool_free(pool, pg); + pool_free(pool, fd); +} + +const char *pg_vfs_io_getetag(SYS_FILE fd) { + PgFile *pg = fd->data; + return pg->etag; +} + +/* -------------------------- VFS_DIRIO functions -------------------------- */ + +static int load_dir(VFSDir *dir, PgDir *pg) { + VFS *vfs = dir->ctx->vfs; + PgVFS *pgvfs = vfs->instance; + PgFile *pgfd = pg->file->data; + PgDir *pgdir = dir->data; + + char resid_param[32]; + snprintf(resid_param, 32, "%" PRId64, pgfd->resource_id); + + const char *param = resid_param; + + PGresult *result = PQexecParams( + pgvfs->connection, + sql_get_children, + 1, // number of parameters + NULL, + ¶m, // param: parent resource_id + NULL, + NULL, + 0); // 0: result in text format + if(!result) return 1; + + pgdir->result = result; + pgdir->nrows = PQntuples(result); + return 0; +} + +int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { + PgDir *pg = dir->data; + if(!pg->result) { + if(load_dir(dir, pg)) { + return 0; + } + } + + if(pg->row >= pg->nrows) { + return 0; // EOF + } + + entry->name = PQgetvalue(pg->result, pg->row, 1); + entry->stat_errno = 0; + entry->stat_extra = NULL; + + if(getstat) { + memset(&entry->stat, 0, sizeof(struct stat)); + + char *iscollection = PQgetvalue(pg->result, pg->row, 2); + char *lastmodified = PQgetvalue(pg->result, pg->row, 3); + char *creationdate = PQgetvalue(pg->result, pg->row, 4); + char *contentlength = PQgetvalue(pg->result, pg->row, 5); + pg_set_stat(&entry->stat, iscollection, lastmodified, creationdate, contentlength); + } + + pg->row++; + return 1; +} + +void pg_vfs_dirio_close(VFS_DIR dir) { + pool_handle_t *pool = dir->ctx->pool; + PgDir *pg = dir->data; + if(pg->result) { + PQclear(pg->result); + } + PgFile *pgfile = pg->file->data; + + pool_free(pool, pgfile); + pool_free(pool, pg->file); + pool_free(pool, pg); + pool_free(pool, dir); +} + +time_t pg_convert_timestamp(const char *timestamp) { + struct tm tm; + if(pg_convert_timestamp_tm(timestamp, &tm)) { + return 0; + } +#ifdef __FreeBSD__ + return timelocal(&tm); +#else + return mktime(&tparts) - timezone; +#endif +} + +int pg_convert_timestamp_tm(const char *timestamp, struct tm *tm) { + // TODO: this is a very basic implementation that needs some work + // format yyyy-mm-dd HH:MM:SS + + memset(tm, 0, sizeof(struct tm)); + size_t len = timestamp ? strlen(timestamp) : 0; + if(len < 19) { + return 1; + } else if(len > 63) { + return 1; + } + + char buf[64]; + memcpy(buf, timestamp, len); + + char *year_str = buf; + year_str[4] = '\0'; + + char *month_str = buf + 5; + month_str[2] = '\0'; + + char *day_str = buf + 8; + day_str[2] = '\0'; + + char *hour_str = buf + 11; + hour_str[2] = '\0'; + + char *minute_str = buf + 14; + minute_str[2] = '\0'; + + char *second_str = buf + 17; + second_str[2] = '\0'; + + tm->tm_year = atoi(year_str) - 1900; + tm->tm_mon = atoi(month_str) - 1; + tm->tm_mday = atoi(day_str); + tm->tm_hour = atoi(hour_str); + tm->tm_min = atoi(minute_str); + tm->tm_sec = atoi(second_str); + + return 0; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/vfs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/vfs.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WS_PG_VFS_H +#define WS_PG_VFS_H + +#include "../../public/nsapi.h" +#include "../../public/vfs.h" + +#include "config.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PG_ETAG_MAXLEN 48 + +typedef struct PgVFS { + ResourceData *pg_resource; + PGconn *connection; + char root_resource_id_str[32]; + int64_t root_resource_id; +} PgVFS; + +typedef struct PgFile { + int64_t resource_id; + int64_t parent_id; + WSBool iscollection; + Oid oid; + int fd; + int oflags; + char etag[PG_ETAG_MAXLEN]; + struct stat s; +} PgFile; + +typedef struct PgDir { + VFSFile *file; + PGresult *result; + int row; + int nrows; +} PgDir; + +void* pg_vfs_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); + +VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb, void *initData); + +VFS* pg_vfs_create_from_resourcedata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata); + + +/* + * Resolve a path into a parent_id and resource name + * + * Only absolute paths are supported, therefore path[0] must be '/' + * + * If the resource is not found, res_errno is set to ENOENT + */ +int pg_resolve_path( + PGconn *connection, + const char *path, + const char *root_id, + int64_t *parent_id, + int64_t *resource_id, + Oid *oid, + const char **resource_name, + WSBool *iscollection, + struct stat *s, + char *etag, + int *res_errno); + +void pg_set_stat( + struct stat *s, + const char *iscollection, + const char *lastmodified, + const char *creationdate, + const char *contentlength); + +int pg_create_file( + VFSContext *ctx, + PgVFS *pg, + const char *path, + int64_t *new_resource_id, + int64_t *res_parent_id, + Oid *oid, + const char **resource_name, + struct stat *s, + WSBool collection); + +int pg_remove_res( + VFSContext *ctx, + PgVFS *pg, + int64_t resource_id, + Oid oid); + +int pg_update_resource(PgVFS *pg, int64_t resource_id, int64_t contentlength); + +SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags); +int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf); +int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf); +VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path); +VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd); +int pg_vfs_mkdir(VFSContext *ctx, const char *path); +int pg_vfs_unlink(VFSContext *ctx, const char *path); +int pg_vfs_rmdir(VFSContext *ctx, const char *path); + + +ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte); +ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte); +ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset); +ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset); +off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence); +off_t pg_vfs_io_tell(SYS_FILE fd); +void pg_vfs_io_close(SYS_FILE fd); +const char *pg_vfs_io_getetag(SYS_FILE fd); + + +int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat); +void pg_vfs_dirio_close(VFS_DIR dir); + +time_t pg_convert_timestamp(const char *timestamp); +int pg_convert_timestamp_tm(const char *timestamp, struct tm *tm); + +#ifdef __cplusplus +} +#endif + +#endif /* WS_PG_VFS_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/webdav.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/webdav.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,1426 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "webdav.h" +#include "vfs.h" +#include "config.h" + +#include "../../util/util.h" +#include "../../util/pblock.h" + +#include "../../daemon/http.h" // etag + +#include +#include +#include + + +static WebdavBackend pg_webdav_backend = { + pg_dav_propfind_init, + pg_dav_propfind_do, + pg_dav_propfind_finish, + pg_dav_proppatch_do, + pg_dav_proppatch_finish, + NULL, // opt_mkcol + NULL, // opt_mkcol_finish + NULL, // opt_delete + NULL, // opt_delete_finish + 0, + NULL, + NULL +}; + + + +/* + * SQL query components + */ + +/* + * PROPFIND queries are build from components: + * + * cte cte_recursive or empty + * select + * ppath ppath column + * cols list of columns + * ext_cols* list of extension columns + * from from [table] / from cte + * prop_join join with property table, allprop or plist + * ext_join* extension table join + * where different where clauses for depth0 and depth1 + * order depth0 doesn't need order + */ + +static const char *sql_propfind_cte_recursive = "\ +with recursive resolvepath as (\n\ + select\n\ + '' as ppath,\n\ + *\n\ + from Resource\n\ + where resource_id = $1 \n\ + union\n\ + select\n\ + p.ppath || '/' || r.nodename,\n\ + r.*\n\ + from Resource r\n\ + inner join resolvepath p on r.parent_id = p.resource_id\n\ + )\n"; + +static const char *sql_propfind_select = "\ +select\n"; + +static const char *sql_propfind_ppath_depth0 = "\ +$2::text as ppath,\n"; + +static const char *sql_propfind_ppath_depth1 = "\ +case when r.resource_id = $1 then $2\n\ + else $2 || '/' || r.nodename\n\ +end as ppath,\n"; + +static const char *sql_propfind_ppath_depth_infinity = "\ +case when r.resource_id = $1 then $2\n\ + else $2 || r.ppath\n\ +end as ppath,\n"; + +static const char *sql_propfind_cols = "\ +r.resource_id,\n\ +r.parent_id,\n\ +r.nodename,\n\ +r.iscollection,\n\ +r.lastmodified,\n\ +r.creationdate,\n\ +r.contentlength,\n\ +r.etag,\n\ +p.prefix,\n\ +p.xmlns,\n\ +p.pname,\n\ +p.lang,\n\ +p.nsdeflist,\n\ +p.pvalue\n"; + +static const char *sql_propfind_from_table = "\ +from Resource r\n"; + +static const char *sql_propfind_from_cte = "\ +from resolvepath r\n"; + +static const char *sql_propfind_propjoin_allprop = "\ +left join Property p on r.resource_id = p.resource_id\n"; + +static const char *sql_propfind_propjoin_plist = "\ +left join (\n\ + select p.* from Property p\ + inner join (select unnest($3::text[]) as xmlns, unnest($4::text[]) as pname) n\n\ + on p.xmlns = n.xmlns and p.pname = n.pname\n\ +) p on r.resource_id = p.resource_id\n"; + +static const char *sql_propfind_where_depth0 = "\ +where r.resource_id = $1\n"; + +static const char *sql_propfind_where_depth1 = "\ +where r.resource_id = $1 or r.parent_id = $1\n"; + +static const char *sql_propfind_order_depth1 = "\ +order by case when r.resource_id = $1 then 0 else 1 end, nodename, resource_id"; + +static const char *sql_propfind_order_depth_infinity = "\ +order by replace(ppath, '/', chr(1)), resource_id"; + +/* + * SQL Queries + */ + + +// proppatch: set property +// params: $1: resource_id +// $2: xmlns prefix +// $3: xmlns href +// $4: property name +// $5: lang attribute value +// $6: namespace list string +// $7: property value +static const char *sql_proppatch_set = "\ +insert into Property(resource_id, prefix, xmlns, pname, lang, nsdeflist, pvalue)\n\ +values($1, $2, $3, $4, $5, $6, $7)\n\ +on conflict (resource_id, xmlns, pname) do\n\ +update set prefix=$2, lang=$5, nsdeflist=$6, pvalue=$7;"; + +// proppatch: remove property +// params: $1: resource_id +// $2: xmlns href +// $3: property name +static const char *sql_proppatch_remove = "\ +delete from Property where resource_id = $1 and xmlns = $2 and pname = $3"; + + +void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { + return pg_init_repo(cfg, pool, config); +} + +WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData) { + PgRepository *repo = initData; + + char *resource_pool; + if(repo) { + resource_pool = repo->resourcepool.ptr; + } else { + // resourcepool is required + resource_pool = pblock_findval("resourcepool", pb); + if(!resource_pool) { + log_ereport(LOG_MISCONFIG, "pg_webdav_create: missing resourcepool parameter"); + return NULL; + } + } + + // get the resource first (should only fail in case of misconfig) + ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0); + if(!resdata) { + log_ereport(LOG_MISCONFIG, "postgresql webdav: resource pool %s not found", resource_pool); + return NULL; + } + + return pg_webdav_create_from_resdata(sn, rq, repo, resdata); +} + +WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) { + WebdavBackend *webdav = pool_malloc(sn->pool, sizeof(WebdavBackend)); + if(!webdav) { + return NULL; + } + *webdav = pg_webdav_backend; + + PgWebdavBackend *instance = pool_malloc(sn->pool, sizeof(PgWebdavBackend)); + if(!instance) { + pool_free(sn->pool, webdav); + return NULL; + } + webdav->instance = instance; + + instance->pg_resource = resdata; + instance->connection = resdata->data; + + instance->repository = repo; + snprintf(instance->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id); + + return webdav; +} + +WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb) { + return NULL; +} + +/* + * adds str to the buffer + * some characters will be escaped: \,{} + */ +static void buf_addstr_escaped(UcxBuffer *buf, const char *str) { + size_t len = strlen(str); + for(size_t i=0;iproperty; + if(property && property->namespace && property->namespace->href && property->name) { + buf_addstr_escaped(xmlns, (const char*)property->namespace->href); + buf_addstr_escaped(pname, (const char*)property->name); + if(plist->next) { + ucx_buffer_putc(xmlns, ','); + ucx_buffer_putc(pname, ','); + } + } + plist = plist->next; + } + int r1 = ucx_buffer_write("}\0", 2, 1, xmlns) == 0; + int r2 = ucx_buffer_write("}\0", 2, 1, pname) == 0; + return r1+r2 != 0; +} + + +static int propfind_ext_cmp(const void *f1, const void *f2) { + const PgPropfindExtCol *e1 = f1; + const PgPropfindExtCol *e2 = f2; + + if(e1->ext->tableindex != e2->ext->tableindex) { + return e1->ext->tableindex < e2->ext->tableindex ? -1 : 1; + } + + return 0; +} + + +static int pg_create_propfind_query( + WebdavPropfindRequest *rq, + WSBool iscollection, + PgPropfindExtCol *ext, + size_t numext, + UcxBuffer *sql) +{ + PgWebdavBackend *pgdav = rq->dav->instance; + PgRepository *repo = pgdav->repository; + int depth = !iscollection ? 0 : rq->depth; + + /* + * PROPFIND queries are build from components: + * + * cte cte_recursive or empty + * select + * ppath ppath column + * cols list of columns + * ext_cols* list of extension columns + * from from [table] / from cte + * prop_join join with property table, allprop or plist + * ext_join* extension table join + * where different where clauses for depth0 and depth1 + * order depth0 doesn't need order + */ + + // CTE + if(depth == -1) { + ucx_buffer_puts(sql, sql_propfind_cte_recursive); + } + + // select + ucx_buffer_puts(sql, sql_propfind_select); + + // ppath + switch(depth) { + case 0: ucx_buffer_puts(sql, sql_propfind_ppath_depth0); break; + case 1: ucx_buffer_puts(sql, sql_propfind_ppath_depth1); break; + case -1: ucx_buffer_puts(sql, sql_propfind_ppath_depth_infinity); break; + } + + // cols + ucx_buffer_puts(sql, sql_propfind_cols); + + // ext_cols + if(ext) { + if(rq->allprop) { + for(int i=0;intables;i++) { + ucx_bprintf(sql, ",x%d.*\n", i); + } + } else { + for(int i=0;itableindex, e.ext->column); + } + } + } + + // from + ucx_buffer_puts(sql, depth == -1 ? sql_propfind_from_cte : sql_propfind_from_table); + + // prop join + ucx_buffer_puts(sql, rq->allprop ? sql_propfind_propjoin_allprop : sql_propfind_propjoin_plist); + + // ext_join + if(ext) { + if(rq->allprop) { + for(int i=0;intables;i++) { + ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[i].table, i, i); + } + } else { + int tab = -1; + for(int i=0;itableindex != tab) { + tab = e.ext->tableindex; + ucx_bprintf(sql, "left join %s x%d on r.resource_id = x%d.resource_id\n", repo->tables[tab].table, tab, tab); + } + } + } + + } + + // where + if(depth == 0) { + ucx_buffer_puts(sql, sql_propfind_where_depth0); + } else if(depth == 1) { + ucx_buffer_puts(sql, sql_propfind_where_depth1); + } + + // order + if(depth == 1) { + ucx_buffer_puts(sql, sql_propfind_order_depth1); + } else if(depth == -1) { + ucx_buffer_puts(sql, sql_propfind_order_depth_infinity); + } + + // end + ucx_buffer_puts(sql, ";\0"); + + return 0; +} + +int pg_dav_propfind_init( + WebdavPropfindRequest *rq, + const char *path, + const char *href, + WebdavPList **outplist) +{ + PgWebdavBackend *pgdav = rq->dav->instance; + + // first, check if the resource exists + // if it doesn't exist, we can return immediately + int64_t parent_id; + int64_t resource_id; + const char *resourcename; + WSBool iscollection; + int res_errno = 0; + int err = pg_resolve_path( + pgdav->connection, + path, + pgdav->root_resource_id_str, + &parent_id, + &resource_id, + NULL, // OID + &resourcename, + &iscollection, + NULL, // stat + NULL, // etag + &res_errno); + + if(err) { + if(res_errno == ENOENT) { + protocol_status(rq->sn, rq->rq, PROTOCOL_NOT_FOUND, NULL); + } + return 1; + } + + // store resource_id in rq->vars, maybe some other modules + // like to use it + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, resource_id); + pblock_nvinsert("resource_id",resource_id_str, rq->rq->vars); + + // create a list of requsted extended properties + PgPropfindExtCol *ext; + size_t numext; + if(pgdav->repository->ntables == 0) { + // no property extensions configured + ext = NULL; + numext = 0; + } else { + numext = pgdav->repository->prop_ext->count; + ext = pool_calloc(rq->sn->pool, numext, sizeof(PgPropfindExtCol)); + + if(rq->allprop) { + // the map pgdav->repository->prop_ext contains all property extensions + // we can just convert the map to an array + UcxMapIterator i = ucx_map_iterator(pgdav->repository->prop_ext); + PgPropertyStoreExt *cfg_ext; + int j = 0; + UCX_MAP_FOREACH(key, cfg_ext, i) { + PgPropfindExtCol extcol; + extcol.ext = cfg_ext; + extcol.field_num = -1; // get the field_num after the PQexec + ext[j++] = extcol; + } + } else { + WebdavPListIterator i = webdav_plist_iterator(outplist); + WebdavPList *cur; + int j = 0; + while(webdav_plist_iterator_next(&i, &cur)) { + WSNamespace *ns = cur->property->namespace; + if(ns) { + UcxKey pkey = webdav_property_key((const char*)ns->href, cur->property->name); + PgPropertyStoreExt *cfg_ext = ucx_map_get(pgdav->repository->prop_ext, pkey); + free((void*)pkey.data); + if(cfg_ext) { + PgPropfindExtCol extcol; + extcol.ext = cfg_ext; + extcol.field_num = -1; // get the field_num after the PQexec + ext[j++] = extcol; + + webdav_plist_iterator_remove_current(&i); + } + } + } + numext = j; + } + + qsort(ext, numext, sizeof(PgPropfindExtCol), propfind_ext_cmp); + } + + // create sql query + const char *query = NULL; + UcxBuffer *sql = ucx_buffer_new(NULL, 2048, UCX_BUFFER_AUTOEXTEND); + if(pg_create_propfind_query(rq, iscollection, ext, numext, sql)) { + return 1; + } + query = sql->space; + + // get all resources and properties + size_t href_len = strlen(href); + char *href_param = pool_malloc(rq->sn->pool, href_len + 1); + memcpy(href_param, href, href_len); + if(href_param[href_len-1] == '/') { + href_len--; + } + href_param[href_len] = '\0'; + + // if allprop is false, create array pair for xmlns/property names + UcxBuffer *xmlns_buf = NULL; + UcxBuffer *pname_buf = NULL; + char *xmlns_param = NULL; + char *pname_param = NULL; + int nparam = 2; + if(!rq->allprop) { + size_t bufsize = rq->propcount < 200 ? 8 + rq->propcount * 32 : 4096; + xmlns_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); + if(!xmlns_buf) { + return 1; + } + pname_buf = ucx_buffer_new(NULL, bufsize, UCX_BUFFER_AUTOEXTEND); + if(!pname_buf) { + ucx_buffer_free(xmlns_buf); + return 1; + } + if(pg_create_property_param_arrays(*outplist, xmlns_buf, pname_buf)) { + ucx_buffer_free(xmlns_buf); + ucx_buffer_free(pname_buf); + return 1; + } + xmlns_param = xmlns_buf->space; + pname_param = pname_buf->space; + nparam = 4; + } + + const char* params[4] = { resource_id_str, href_param, xmlns_param, pname_param }; + PGresult *result = PQexecParams( + pgdav->connection, + query, + nparam, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + int nrows = PQntuples(result); + pool_free(rq->sn->pool, href_param); + if(xmlns_buf) { + ucx_buffer_free(xmlns_buf); + ucx_buffer_free(pname_buf); + } + if(nrows < 1) { + PQclear(result); + return 1; + } + + PgPropfind *pg = pool_malloc(rq->sn->pool, sizeof(PgPropfind)); + rq->userdata = pg; + + pg->path = path; + pg->resource_id = resource_id; + pg->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0); + pg->result = result; + pg->nrows = nrows; + + pg->ext = ext; + pg->numext = numext; + if(ext) { + // get field_nums for all property extensions + for(int i=0;ifield_num = PQfnumber(result, c->ext->column); + } + } + + return 0; +} + +int pg_dav_propfind_do( + WebdavPropfindRequest *rq, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + PgPropfind *pg = rq->userdata; + pool_handle_t *pool = rq->sn->pool; + PGresult *result = pg->result; + WebdavVFSProperties vfsprops = pg->vfsproperties; + + WSBool vfsprops_set = 0; // are live properties added to the response? + WSBool extprops_set = 0; // are extended properties added to the response? + int64_t current_resource_id = pg->resource_id; + for(int r=0;rnrows;r++) { + // columns: + // 0: path + // 1: resource_id + // 2: parent_id + // 3: nodename + // 4: iscollection + // 5: lastmodified + // 6: creationdate + // 7: contentlength + // 8: etag + // 9: property prefix + // 10: property xmlns + // 11: property name + // 12: property lang + // 13: property nsdeflist + // 14: property value + + char *path = PQgetvalue(result, r, 0); + char *res_id = PQgetvalue(result, r, 1); + char *iscollection_str = PQgetvalue(result, r, 4); + WSBool iscollection = iscollection_str && iscollection_str[0] == 't'; + int64_t resource_id; + if(!util_strtoint(res_id, &resource_id)) { + log_ereport(LOG_FAILURE, "pg_dav_propfind_do: cannot convert resource_id '%s' to int", res_id); + return 1; + } + + if(resource_id != current_resource_id) { + // create a href string for the new resource + // if the resource is a collection, it should have a trailing '/' + size_t pathlen = strlen(path); + if(pathlen == 0) { + log_ereport(LOG_FAILURE, "pg_dav_propfind_do: query returned invalid path"); + return 1; + } + if(pathlen > PG_MAX_PATH_LEN) { + log_ereport(LOG_FAILURE, "pg_dav_propfind_do: path too long: resource_id: %s", res_id); + return 1; + } + char *newres_href = pool_malloc(pool, (pathlen*3)+2); + util_uri_escape(newres_href, path); + if(iscollection && path[pathlen-1] != '/') { + size_t newres_href_len = strlen(newres_href); + newres_href[newres_href_len] = '/'; + newres_href[newres_href_len+1] = '\0'; + } + + // new resource + resource = response->addresource(response, newres_href); + vfsprops_set = FALSE; + extprops_set = FALSE; + current_resource_id = resource_id; + } + + // standard webdav live properties + if(!vfsprops_set) { + if(vfsprops.getresourcetype) { + if(iscollection) { + resource->addproperty(resource, webdav_resourcetype_collection(), 200); + } else { + resource->addproperty(resource, webdav_resourcetype_empty(), 200); + } + } + + char *lastmodified = PQgetvalue(result, r, 5); + char *contentlength = PQgetvalue(result, r, 7); + time_t t = pg_convert_timestamp(lastmodified); + + if(vfsprops.getlastmodified) { + struct tm tm; + gmtime_r(&t, &tm); + + char buf[HTTP_DATE_LEN+1]; + strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, &tm); + webdav_resource_add_dav_stringproperty(resource, pool, "getlastmodified", buf, strlen(buf)); + } + if(vfsprops.creationdate) { + char *creationdate = PQgetvalue(result, r, 6); + webdav_resource_add_dav_stringproperty(resource, pool, "creationdate", creationdate, strlen(creationdate)); + } + if(vfsprops.getcontentlength && !iscollection) { + webdav_resource_add_dav_stringproperty(resource, pool, "getcontentlength", contentlength, strlen(contentlength)); + } + if(vfsprops.getetag) { + char *etag = PQgetvalue(result, r, 8); + if(!PQgetisnull(result, r, 8)) { + webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag)); + } else { + int64_t ctlen; + if(util_strtoint(contentlength, &ctlen)) { + char etag[MAX_ETAG]; + http_format_etag(rq->sn, rq->rq, etag, MAX_ETAG, ctlen, t); + webdav_resource_add_dav_stringproperty(resource, pool, "getetag", etag, strlen(etag)); + } + } + } + + vfsprops_set = TRUE; + } + + if(!extprops_set) { + // extended properties + if(pg->ext) { + for(int extc=0;extcnumext;extc++) { + PgPropfindExtCol ext = pg->ext[extc]; + int fieldnum = ext.field_num; + + if(!PQgetisnull(result, r, fieldnum)) { + char *ext_value = PQgetvalue(result, r, fieldnum); + int ext_value_len = PQgetlength(result, r, fieldnum); + char ext_xmlns_prefix[32]; + snprintf(ext_xmlns_prefix, 32, "x%d", ext.ext->tableindex); + + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + property->lang = NULL; + property->name = pool_strdup(pool, ext.ext->name); + + xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs)); + memset(namespace, 0, sizeof(struct _xmlNs)); + namespace->href = (xmlChar*)pool_strdup(pool, ext.ext->ns); + namespace->prefix = (xmlChar*)pool_strdup(pool, ext_xmlns_prefix); + property->namespace = namespace; + + char *content = pool_malloc(pool, ext_value_len+1); + memcpy(content, ext_value, ext_value_len); + content[ext_value_len] = '\0'; + + WebdavNSList *nslist = pool_malloc(pool, sizeof(WebdavNSList)); + nslist->namespace = namespace; + nslist->prev = NULL; + nslist->next = NULL; + + property->vtype = WS_VALUE_XML_DATA; + property->value.data.data = content; + property->value.data.length = ext_value_len; + property->value.data.namespaces = nslist; + + resource->addproperty(resource, property, 200); + } + } + } + + extprops_set = TRUE; + } + + // dead properties + if(!PQgetisnull(result, r, 9)) { + char *prefix = PQgetvalue(result, r, 9); + char *xmlns = PQgetvalue(result, r, 10); + char *pname = PQgetvalue(result, r, 11); + char *lang = PQgetvalue(result, r, 12); + char *nsdef = PQgetvalue(result, r, 13); + char *pvalue = PQgetvalue(result, r, 14); + + int pvalue_len = PQgetlength(result, r, 14); + WSBool lang_isnull = PQgetisnull(result, r, 12); + WSBool nsdef_isnull = PQgetisnull(result, r, 13); + WSBool pvalue_isnull = PQgetisnull(result, r, 14); + + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + property->lang = NULL; + property->name = pool_strdup(pool, pname); + + xmlNs *namespace = pool_malloc(pool, sizeof(xmlNs)); + memset(namespace, 0, sizeof(struct _xmlNs)); + namespace->href = (xmlChar*)pool_strdup(pool, xmlns); + namespace->prefix = (xmlChar*)pool_strdup(pool, prefix); + property->namespace = namespace; + + if(!lang_isnull) { + property->lang = pool_strdup(pool, lang); + } + + if(!pvalue_isnull) { + char *content = pool_malloc(pool, pvalue_len+1); + memcpy(content, pvalue, pvalue_len); + content[pvalue_len] = '\0'; + + if(nsdef_isnull) { + property->vtype = WS_VALUE_TEXT; + property->value.text.str = content; + property->value.text.length = pvalue_len; + } else { + WebdavNSList *nslist = wsxml_string2nslist(pool, nsdef); + property->vtype = WS_VALUE_XML_DATA; + property->value.data.data = content; + property->value.data.length = pvalue_len; + property->value.data.namespaces = nslist; + + } + } + + resource->addproperty(resource, property, 200); + } + } + + return 0; +} + +int pg_dav_propfind_finish(WebdavPropfindRequest *rq) { + PgPropfind *pg = rq->userdata; + pool_handle_t *pool = rq->sn->pool; + PGresult *result = pg->result; + + PQclear(result); + + return 0; +} + +enum PgDavProp { + PG_DAV_PROPPATCH_NOT_ALLOWED = 0, + PG_DAV_CREATIONDATE, + PG_DAV_DISPLAYNAME, + PG_DAV_DEADPROP +}; +/* + * checks if the property can be manipulated + */ +static enum PgDavProp proppatch_check_dav_prop(const char *name) { + if(!strcmp(name, "getlastmodified")) { + return PG_DAV_PROPPATCH_NOT_ALLOWED; + } else if(!strcmp(name, "getcontentlength")) { + return PG_DAV_PROPPATCH_NOT_ALLOWED; + } else if(!strcmp(name, "resourcetype")) { + return PG_DAV_PROPPATCH_NOT_ALLOWED; + } else if(!strcmp(name, "getetag")) { + return PG_DAV_PROPPATCH_NOT_ALLOWED; + } else if(!strcmp(name, "creationdate")) { + return PG_DAV_CREATIONDATE; + } else if(!strcmp(name, "displayname")) { + return PG_DAV_DISPLAYNAME; + } + return PG_DAV_DEADPROP; +} + +typedef struct { + WebdavProperty *creationdate; + WebdavProperty *displayname; + int error; +} PgProppatchOpResult; + +typedef int(*pg_proppatch_func)(PgWebdavBackend*, WebdavProppatchRequest*, WebdavResource*, WebdavProperty*, void*); + +/* + * This function iterates the property list 'plist', + * analyses if any DAV: property is in the list + * and calls opfunc for the each property + * + * If the property list contains the properties creationdate or displayname, + * the pointers to these properties will be stored in the result structure + */ +static PgProppatchOpResult pg_proppatch_op( + PgWebdavBackend *pgdav, + WebdavProppatchRequest *request, + WebdavResource *response, + WebdavPList **plist, + enum PgDavProp forbidden_extra, + pg_proppatch_func opfunc, + void *op_userdata) +{ + PgProppatchOpResult result; + result.creationdate = NULL; + result.displayname = NULL; + result.error = 0; + + WebdavPListIterator i = webdav_plist_iterator(plist); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + WebdavProperty *property = cur->property; + WSNamespace *ns = property->namespace; + if(!ns) { + continue; // maybe we should abort + } + + // check if the property is a DAV: property that requires special + // handling + // get* properties can't be manipulated + // some properties can't be removed + if(!strcmp((const char*)ns->href, "DAV:")) { + const char *name = property->name; + enum PgDavProp davprop = proppatch_check_dav_prop(name); + if(davprop != PG_DAV_DEADPROP) { + if(davprop == PG_DAV_PROPPATCH_NOT_ALLOWED || davprop == forbidden_extra) { + response->addproperty(response, property, 409); + } else if(davprop == PG_DAV_CREATIONDATE) { + result.creationdate = property; + } else if(davprop == PG_DAV_DISPLAYNAME) { + result.displayname = property; + } + webdav_plist_iterator_remove_current(&i); + continue; + } + } + + // call op func (set, remove specific code) + if(opfunc(pgdav, request, response, property, op_userdata)) { + result.error = 1; + break; + } + + webdav_plist_iterator_remove_current(&i); + } + + return result; +} + +#define PG_PROPPATCH_EXT_SET 0 +#define PG_PROPPATCH_EXT_REMOVE 1 + +static int pg_proppatch_add_ext_prop( + pool_handle_t *pool, + PgWebdavBackend *pgdav, + PgProppatch *proppatch, + WebdavProperty *property, + int proppatch_op) +{ + UcxKey pkey = webdav_property_key((const char*)property->namespace->href, property->name); + PgPropertyStoreExt *ext = ucx_map_get(pgdav->repository->prop_ext, pkey); + free((void*)pkey.data); + if(ext) { + PgProppatchExtProp *ext_prop = pool_malloc(pool, sizeof(PgProppatchExtProp)); + if(!ext_prop) { + return 1; + } + ext_prop->column = ext; + ext_prop->property = property; + + UcxAllocator a = util_pool_allocator(pool); + proppatch->ext[ext->tableindex].isused = TRUE; + + UcxList **list = proppatch_op == PG_PROPPATCH_EXT_REMOVE + ? &proppatch->ext[ext->tableindex].remove + : &proppatch->ext[ext->tableindex].set; + *list = ucx_list_append_a(&a, *list, ext_prop); + + proppatch->extensions_used = TRUE; + } + + return 0; +} + +static int pg_dav_set_property( + PgWebdavBackend *pgdav, + WebdavProppatchRequest *request, + WebdavResource *response, + WebdavProperty *property, + void *userdata) +{ + pool_handle_t *pool = request->sn->pool; + PgProppatch *proppatch = request->userdata; + WSNamespace *ns = property->namespace; + + // check if the property belongs to an extension + if(proppatch->ext && ns) { + return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_SET); + } + + char *resource_id_str = userdata; + int ret = 0; + + // convert the property value to WSXmlData + // property->vtype == WS_VALUE_XML_NODE should always be true + WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; + char *value_str = NULL; + char *nsdef_str = NULL; + if(property_value) { + value_str = property_value->data; + if(property_value->namespaces) { + nsdef_str = wsxml_nslist2string(pool, property_value->namespaces); + if(!nsdef_str) { + return 1; // OOM + } + } + } + + // exec sql + const char* params[7] = { resource_id_str, (const char*)ns->prefix, (const char*)ns->href, property->name, NULL, nsdef_str, value_str}; + PGresult *result = PQexecParams( + pgdav->connection, + sql_proppatch_set, + 7, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + response->addproperty(response, property, 500); + //printf(PQerrorMessage(pgdav->connection)); + //fflush(stdout); + ret = 1; + } else { + response->addproperty(response, property, 200); + } + PQclear(result); + if(value_str) pool_free(pool, value_str); + + return ret; +} + + +static int pg_dav_remove_property( + PgWebdavBackend *pgdav, + WebdavProppatchRequest *request, + WebdavResource *response, + WebdavProperty *property, + void *userdata) +{ + pool_handle_t *pool = request->sn->pool; + PgProppatch *proppatch = request->userdata; + WSNamespace *ns = property->namespace; + + // check if the property belongs to an extension + if(proppatch->ext && ns) { + return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_REMOVE); + } + + char *resource_id_str = userdata; + int ret = 0; + + // exec sql + const char* params[3] = { resource_id_str, (const char*)ns->href, property->name }; + PGresult *result = PQexecParams( + pgdav->connection, + sql_proppatch_remove, + 3, // number of parameters + NULL, + params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + response->addproperty(response, property, 500); + //printf(PQerrorMessage(pgdav->connection)); + //fflush(stdout); + ret = 1; + } + PQclear(result); + + return ret; +} + + +/* + * Creates an SQL query for inserting a new row to an extension table + * A parameter list for PQexecParams will also be generated, however + * params[0] will be empty (resource_id str) + * + * Query: insert into (resource_id, col1, ...) values ($1, $2 ...); + */ +static UcxBuffer* ext_row_create_insert_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) { + pool_handle_t *pool = request->sn->pool; + + UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + if(!sql) { + return NULL; + } + + size_t pg_nparams = ucx_list_size(ext->set) + 1; + char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*)); + if(!pg_params) { + ucx_buffer_free(sql); + return NULL; + } + + ucx_buffer_puts(sql, "insert into "); + ucx_buffer_puts(sql, table->table); + ucx_buffer_puts(sql, "(resource_id"); + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + ucx_bprintf(sql, ",%s", prop->column->name); + } + + ucx_buffer_puts(sql, ") values ($1\n"); + int i = 1; + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + WebdavProperty *property = prop->property; + // convert the property value to WSXmlData + // property->vtype == WS_VALUE_XML_NODE should always be true + WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; + char *value_str = NULL; + //char *nsdef_str = NULL; + if(property_value) { + value_str = property_value->data; + if(property_value->namespaces) { + // currently only text data is supported + pool_free(pool, params); + ucx_buffer_free(sql); + return NULL; + } + } + + pg_params[i] = value_str; + ucx_bprintf(sql, ",$%d", ++i); + } + ucx_buffer_puts(sql, ");"); + + + //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); + //fflush(stdout); + + *params = pg_params; + *nparams = pg_nparams; + + return sql; +} + +/* + * Creates an SQL query for updating an extension table row + * A parameter list for PQexecParams will also be generated, however + * params[0] will be empty (resource_id str) + * + * Query: update
set + * col1 = $2, + * col2 = $3, + * ... + * where resource_id = $1 ; + */ +static UcxBuffer* ext_row_create_update_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) { + pool_handle_t *pool = request->sn->pool; + + UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + if(!sql) { + return NULL; + } + + ucx_buffer_puts(sql, "update "); + ucx_buffer_puts(sql, table->table); + ucx_buffer_puts(sql, " set\n"); + + size_t pg_nparams = ucx_list_size(ext->set) + 1; + char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*)); + if(!pg_params) { + ucx_buffer_free(sql); + return NULL; + } + + int i = 1; + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + WebdavProperty *property = prop->property; + // convert the property value to WSXmlData + // property->vtype == WS_VALUE_XML_NODE should always be true + WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; + char *value_str = NULL; + //char *nsdef_str = NULL; + if(property_value) { + value_str = property_value->data; + if(property_value->namespaces) { + // currently only text data is supported + pool_free(pool, params); + ucx_buffer_free(sql); + return NULL; + } + } + + pg_params[i] = value_str; + ucx_bprintf(sql, " %s = $%d,\n", prop->column->name, ++i); + } + + UCX_FOREACH(elm, ext->remove) { + PgProppatchExtProp *prop = elm->data; + ucx_bprintf(sql, " %s = NULL,\n", prop->column->name); + } + + // check if any write worked + if(sql->pos == 0) { + ucx_buffer_free(sql); + pool_free(pool, pg_params); + return NULL; + } + + // last line should end with ',' '\n' + // replace ',' with space + if(sql->space[sql->pos-2] == ',') { + sql->space[sql->pos-2] = ' '; + } + + ucx_bprintf(sql, "where resource_id = $1 ;\0"); + + //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); + //fflush(stdout); + + *params = pg_params; + *nparams = pg_nparams; + + return sql; +} + +/* + * Executes an SQL insert for the extension table + */ +int ext_row_insert(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + pool_handle_t *pool = request->sn->pool; + + char **params; + size_t nparam; + UcxBuffer *sql = ext_row_create_insert_query(request, ext, table, ¶ms, &nparam); + if(!sql) { + return 1; + } + + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id); + params[0] = resource_id_str; + + PGresult *result = PQexecParams( + pgdav->connection, + sql->space, + nparam, // number of parameters + NULL, + ( const char *const *)params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + ucx_buffer_free(sql); + + int ret = 1; + if(PQresultStatus(result) == PGRES_COMMAND_OK) { + // command ok, check if any row was updated + char *nrows_affected = PQcmdTuples(result); + if(nrows_affected[0] == '1') { + ret = 0; + } else { + log_ereport(LOG_FAILURE, "pg: extension row insert failed"); + } + } else { + log_ereport(LOG_FAILURE, "pg: extension row insert failed: %s", PQresultErrorMessage(result)); + } + + PQclear(result); + + return ret; +} + +/* + * Executes an SQL update for the extension table + */ +int ext_row_update(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + pool_handle_t *pool = request->sn->pool; + + char **params; + size_t nparam; + UcxBuffer *sql = ext_row_create_update_query(request, ext, table, ¶ms, &nparam); + if(!sql) { + return 1; + } + + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id); + params[0] = resource_id_str; + + PGresult *result = PQexecParams( + pgdav->connection, + sql->space, + nparam, // number of parameters + NULL, + ( const char *const *)params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + ucx_buffer_free(sql); + + int ret = 1; + if(PQresultStatus(result) == PGRES_COMMAND_OK) { + // command ok, check if any row was updated + char *nrows_affected = PQcmdTuples(result); + if(nrows_affected[0] == '1') { + ret = 0; + } else if(nrows_affected[0] == '0') { + // no rows affected, that means we have to insert a new row + // in the extension table for this resource + + // TODO: cleanup params + + ret = ext_row_insert(request, ext, table); + } + } else { + log_ereport(LOG_FAILURE, "pg: extension row update failed: %s", PQresultErrorMessage(result)); + } + + + PQclear(result); + + return ret; +} + +static int pg_dav_update_extension_tables(WebdavProppatchRequest *request) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + + for(int i=0;inumext;i++) { + if(proppatch->ext[i].isused) { + if(ext_row_update(request, &proppatch->ext[i], &pgdav->repository->tables[i])) { + // extension proppatch failed + return 1; + } + } + } + + return 0; +} + +int pg_dav_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove) +{ + PgWebdavBackend *pgdav = request->dav->instance; + pool_handle_t *pool = request->sn->pool; + char *path = pblock_findkeyval(pb_key_path, request->rq->vars); + + PgProppatch proppatch; + proppatch.extensions_used = FALSE; + if(pgdav->repository->ntables == 0) { + proppatch.ext = NULL; + proppatch.numext = 0; + } else { + // some properties are stored in additional tables + // for each table we create a PgProppatchExt record + // which stores data about, which tables are used + // and which properties (columns) should be updated + // + // proppatch.ext[i] should contain the data for repository->tables[i] + proppatch.numext = pgdav->repository->ntables; + proppatch.ext = pool_calloc(request->sn->pool, proppatch.numext, sizeof(PgProppatchExt)); + if(!proppatch.ext) { + return 1; // OOM + } + } + request->userdata = &proppatch; + + // check if the resource exists, we also need the resource_id + int64_t parent_id; + int64_t resource_id; + const char *resourcename; + WSBool iscollection; + int res_errno = 0; + int err = pg_resolve_path( + pgdav->connection, + path, + pgdav->root_resource_id_str, + &parent_id, + &resource_id, + NULL, // OID + &resourcename, + &iscollection, + NULL, // stat + NULL, // etag + &res_errno); + + if(err) { + return 1; + } + + proppatch.resource_id = resource_id; + + // because proppatch must be atomic and we have multiple sql + // queries and other backends that do stuff that could fail + // we need the possibility to reverse all changes + // we use a transaction savepoint for this + PGresult *result = PQexec(pgdav->connection, "savepoint proppatch;"); + ExecStatusType execStatus = PQresultStatus(result); + PQclear(result); + if(execStatus != PGRES_COMMAND_OK) { + return 1; + } + + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, resource_id); + // store the resource_id in rq->vars, because it could be useful later + pblock_nvinsert("resource_id", resource_id_str, request->rq->vars); + + int ret = 0; + PgProppatchOpResult set_res = pg_proppatch_op( + pgdav, + request, + response, + out_set, + PG_DAV_PROPPATCH_NOT_ALLOWED, + pg_dav_set_property, + resource_id_str); + if(set_res.error) { + return 1; + } + PgProppatchOpResult rm_res = pg_proppatch_op( + pgdav, + request, + response, + out_remove, + PG_DAV_CREATIONDATE, // creationdate can't be removed + pg_dav_remove_property, + resource_id_str); + if(rm_res.error) { + return 1; + } + + // if extensions are in use and pg_proppatch_op found any + // properties, that should be stored in extension tables + // we do the update/insert now + if(proppatch.extensions_used) { + ret = pg_dav_update_extension_tables(request); + } + + + return ret; +} + +int pg_dav_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + PgWebdavBackend *pgdav = request->dav->instance; + int ret = 0; + if(!commit) { + log_ereport(LOG_VERBOSE, "proppatch: rollback"); + PGresult *result = PQexec(pgdav->connection, "rollback to savepoint proppatch;"); + if(PQresultStatus(result) != PGRES_COMMAND_OK) { + log_ereport(LOG_FAILURE, "pg_dav_proppatch_finish: rollback failed: %s", PQerrorMessage(pgdav->connection)); + ret = 1; + } + PQclear(result); + } + return ret; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/plugins/postgresql/webdav.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/plugins/postgresql/webdav.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,134 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WS_PG_WEBDAV_H +#define WS_PG_WEBDAV_H + +#include "../../public/nsapi.h" +#include "../../public/webdav.h" + +#include "config.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PG_MAX_PATH_LEN 0x8000 + +typedef struct PgWebdavBackend { + ResourceData *pg_resource; + PGconn *connection; + PgRepository *repository; + char root_resource_id_str[32]; +} PgWebdavBackend; + +typedef struct PgPropfindExtCol { + /* + * property extension config + */ + PgPropertyStoreExt *ext; + /* + * Result field number + */ + int field_num; +} PgPropfindExtCol; + +typedef struct PgPropfind { + const char *path; + int64_t resource_id; + WebdavVFSProperties vfsproperties; + PGresult *result; + PgPropfindExtCol *ext; + size_t numext; + int nrows; +} PgPropfind; + +typedef struct { + PgPropertyStoreExt *column; + WebdavProperty *property; +} PgProppatchExtProp; + +typedef struct { + UcxList *set; /* list of PgProppatchExtProp* */ + UcxList *remove; /* list of PgProppatchExtProp* */ + WSBool isused; +} PgProppatchExt; + +typedef struct { + int64_t resource_id; + PgProppatchExt *ext; + size_t numext; + WSBool extensions_used; +} PgProppatch; + +void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); +WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData); +WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata); + +WebdavBackend* pg_webdav_prop_create(Session *sn, Request *rq, pblock *pb); + +int pg_create_property_param_arrays(WebdavPList *plist, UcxBuffer *xmlns, UcxBuffer *pname); + +/* ----------------- webdav backend functions ----------------- */ +int pg_dav_propfind_init( + WebdavPropfindRequest *rq, + const char *path, + const char *href, + WebdavPList **outplist); + +int pg_dav_propfind_do( + WebdavPropfindRequest *rq, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s); + +int pg_dav_propfind_finish(WebdavPropfindRequest *rq); + +int pg_dav_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove); + +int pg_dav_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit); + +#ifdef __cplusplus +} +#endif + +#endif /* PG_WEBDAV_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/public/acl.h --- a/src/server/public/acl.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/public/acl.h Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff -r 21274e5950af -r a1f4cb076d2f src/server/public/auth.h --- a/src/server/public/auth.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/public/auth.h Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff -r 21274e5950af -r a1f4cb076d2f src/server/public/nsapi.h --- a/src/server/public/nsapi.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/public/nsapi.h Sat Sep 24 16:26:10 2022 +0200 @@ -110,6 +110,9 @@ /* --- Begin miscellaneous definitions --- */ +#define WS_TRUE 1 +#define WS_FALSE 0 + /* Used in some places as a length limit on error messages */ #define MAGNUS_ERROR_LEN 1024 @@ -355,9 +358,6 @@ #ifndef HPUX #include #endif -#ifndef BSD -#include /* new */ -#endif #include #include #include @@ -404,6 +404,7 @@ // they are VFSFile* // TODO: fix NOTE +typedef int WSBool; #ifndef SYS_FILE_T typedef struct VFSFile *SYS_FILE; @@ -690,6 +691,15 @@ typedef struct VFS VFS; typedef struct VFSContext VFSContext; +enum WSConfigNodeType { + WS_CONFIG_NODE_OBJECT = 0, + WS_CONFIG_NODE_DIRECTIVE +}; + +typedef struct ServerConfiguration ServerConfiguration; +typedef struct ConfigNode WSConfigNode; +typedef enum WSConfigNodeType WSConfigNodeType; + #ifndef PR_AF_INET typedef union PRNetAddr PRNetAddr; #endif @@ -763,6 +773,36 @@ typedef struct _http_listener HttpListener; +typedef struct ResourcePool ResourcePool; + +typedef struct ResourceType ResourceType; +typedef struct ResourceData ResourceData; + +typedef void * (*resource_pool_init_func)(pool_handle_t *, const char *, pblock *); +typedef void (*resource_pool_destroy_func)(void *); +typedef void * (*resource_pool_createresource_func)(void *); +typedef void (*resource_pool_freeresource_func)(void *, void *); +typedef int (*resource_pool_prepare_func)(void *, void *); +typedef int (*resource_pool_finish_func)(void *, void *); +typedef void * (*resource_pool_getresourcedata_func)(void *); + +struct ResourceType { + void * (*init)(pool_handle_t *, const char *, pblock *); + void (*destroy)(void *); + void * (*createresource)(void *); + void (*freeresource)(void *, void *); + int (*prepare)(void *, void *); + int (*finish)(void *, void *); + void * (*getresourcedata)(void *); +}; + +struct ResourceData { + ResourcePool *resourcepool; + void *data; +}; + +int resourcepool_register_type(const char *type_name, ResourceType *type_info); + ////// /* * VSInitFunc, VSDestroyFunc, VSDirectiveInitFunc and VSDirectiveDestroyFunc @@ -1483,6 +1523,7 @@ NSAPI_PUBLIC void http_format_etag(Session *sn, Request *rq, char *etagp, int etaglen, off_t size, time_t mtime); NSAPI_PUBLIC int http_check_preconditions(Session *sn, Request *rq, struct tm *mtm, const char *etag); NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo); +NSAPI_PUBLIC int http_set_finfo_etag(Session *sn, Request *rq, struct stat *finfo, const char *etag); NSAPI_PUBLIC char **http_hdrs2env(pblock *pb); @@ -1568,6 +1609,7 @@ // threadpool threadpool_t* threadpool_new(int min, int max); +int threadpool_start(threadpool_t *pool); void* threadpool_func(void *data); threadpool_job* threadpool_get_job(threadpool_t *pool); void threadpool_run(threadpool_t *pool, job_callback_f func, void *data); @@ -1577,6 +1619,14 @@ int event_removepoll(EventHandler *ev, SYS_NETFD fd); int event_send(EventHandler *ev, Event *event); +// resource pool +ResourceData* resourcepool_lookup(Session *sn, Request *rq, const char *name, int flags); +ResourceData* resourcepool_cfg_lookup(ServerConfiguration *cfg, const char *name, int flags); +void resourcepool_free(Session *sn, Request *rq, ResourceData *resource); + +// utils +NSAPI_PUBLIC char *util_html_escape(const char *s); + // assert void ws_log_assert(const char *file, const char *func, int line); #ifdef _DEBUG diff -r 21274e5950af -r a1f4cb076d2f src/server/public/vfs.h --- a/src/server/public/vfs.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/public/vfs.h Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -48,13 +48,16 @@ #define VFS_ENTRY VFSEntry struct VFS { - SYS_FILE (*open)(VFSContext *ctx, char *path, int oflags); - int (*stat)(VFSContext *ctx, char *path, struct stat *buf); + SYS_FILE (*open)(VFSContext *ctx, const char *path, int oflags); + int (*stat)(VFSContext *ctx, const char *path, struct stat *buf); int (*fstat)(VFSContext *ctx, SYS_FILE fd, struct stat *buf); - VFS_DIR (*opendir)(VFSContext *ctx, char *path); - int (*mkdir)(VFSContext *ctx, char *path); - int (*unlink)(VFSContext *ctx, char *path); + VFS_DIR (*opendir)(VFSContext *ctx, const char *path); + VFS_DIR (*fdopendir)(VFSContext *ctx, SYS_FILE fd); + int (*mkdir)(VFSContext *ctx, const char *path); + int (*unlink)(VFSContext *ctx, const char *path); + int (*rmdir)(VFSContext *Ctx, const char *path); uint32_t flags; + void *instance; }; struct VFSContext { @@ -66,20 +69,21 @@ ACLListHandle *acllist; uint32_t aclreqaccess; int vfs_errno; + WSBool error_response_set; }; struct VFSFile { VFSContext *ctx; - VFS_IO *io; // IO functions - void *data; // private data used by the VFSFile implementation - int fd; // native file descriptor if available, or -1 + VFS_IO *io; /* IO functions */ + void *data; /* private data used by the VFSFile implementation */ + int fd; /* native file descriptor if available, or -1 */ }; struct VFSDir { VFSContext *ctx; VFS_DIRIO *io; - void *data; // private data used by the VFSDir implementation - int fd; // native file descriptor if available, or -1 + void *data; /* private data used by the VFSDir implementation */ + int fd; /* native file descriptor if available, or -1 */ }; struct VFSEntry { @@ -98,6 +102,7 @@ void (*close)(SYS_FILE fd); int (*opt_aioread)(aiocb_s *aiocb); int (*opt_aiowrite)(aiocb_s *aiocb); + const char* (*opt_getetag)(SYS_FILE fd); }; struct VFS_DIRIO { @@ -105,10 +110,18 @@ void (*close)(VFS_DIR dir); }; +typedef void*(*vfs_init_func)(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); +typedef VFS*(*vfs_create_func)(Session *sn, Request *rq, pblock *pb, void *initData); + /* * registers a new VFS */ -void vfs_add(char *name, VFS *vfs); +int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate); + +/* + * Create a new VFS instance + */ +VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData); /* * creates a VFSContext for a Request @@ -116,19 +129,22 @@ */ VFSContext* vfs_request_context(Session *sn, Request *rq); -SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags); -SYS_FILE vfs_openRO(VFSContext *ctx, char *path); -SYS_FILE vfs_openWO(VFSContext *ctx, char *path); -SYS_FILE vfs_openRW(VFSContext *ctx, char *path); -int vfs_stat(VFSContext *ctx, char *path, struct stat *buf); +SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags); +SYS_FILE vfs_openRO(VFSContext *ctx, const char *path); +SYS_FILE vfs_openWO(VFSContext *ctx, const char *path); +SYS_FILE vfs_openRW(VFSContext *ctx, const char *path); +int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf); int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf); +const char * vfs_getetag(SYS_FILE fd); void vfs_close(SYS_FILE fd); -VFS_DIR vfs_opendir(VFSContext *ctx, char *path); +VFS_DIR vfs_opendir(VFSContext *ctx, const char *path); +VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd); int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry); int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry); void vfs_closedir(VFS_DIR dir); -int vfs_mkdir(VFSContext *ctx, char *path); -int vfs_unlink(VFSContext *ctx, char *path); +int vfs_mkdir(VFSContext *ctx, const char *path); +int vfs_unlink(VFSContext *ctx, const char *path); +int vfs_rmdir(VFSContext *ctx, const char *path); #ifdef __cplusplus } diff -r 21274e5950af -r a1f4cb076d2f src/server/public/webdav.h --- a/src/server/public/webdav.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/public/webdav.h Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2020 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,6 +30,7 @@ #define WS_WEBDAV_H #include "nsapi.h" +#include "vfs.h" #include #include @@ -39,7 +40,463 @@ extern "C" { #endif +typedef struct WebdavBackend WebdavBackend; + +typedef struct WebdavProperty WebdavProperty; +typedef struct WebdavPList WebdavPList; +typedef struct WebdavNSList WebdavNSList; +typedef struct WebdavPListIterator WebdavPListIterator; + +typedef enum WebdavLockScope WebdavLockScope; +typedef enum WebdavLockType WebdavLockType; + +typedef enum WebdavValueType WebdavValueType; + +typedef struct WebdavPropfindRequest WebdavPropfindRequest; +typedef struct WebdavProppatchRequest WebdavProppatchRequest; +typedef struct WebdavLockRequest WebdavLockRequest; + +typedef struct WebdavVFSRequest WebdavVFSRequest; + +typedef struct WebdavResponse WebdavResponse; +typedef struct WebdavResource WebdavResource; + +typedef struct WebdavVFSProperties WebdavVFSProperties; + +typedef struct WebdavOperation WebdavOperation; + +typedef struct WSXmlData WSXmlData; +typedef struct WSText WSText; + +typedef struct _xmlNs WSNamespace; +typedef struct _xmlNode WSXmlNode; + +#define WS_NODE_ELEMENT 1 +#define WS_NODE_TEXT 3 +#define WS_NODE_CDATA 4 +#define WS_NODE_ENTITY_REF 5 + +typedef int(*wsxml_func)(WSXmlNode *, void *); + +/* propfind settings */ + +/* + * Use the vfs to stat files or read the directory children + */ +#define WS_WEBDAV_PROPFIND_USE_VFS 0x01 + +/* + * Use the vfs to open a file for proppatch + */ +#define WS_WEBDAV_PROPPATCH_USE_VFS 0x02 + + +typedef void*(*webdav_init_func)(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); +typedef WebdavBackend*(*webdav_create_func)(Session *sn, Request *rq, pblock *pb, void *initData); + +enum WebdavValueType { + WS_VALUE_NO_TYPE = 0, + WS_VALUE_XML_NODE, + WS_VALUE_XML_DATA, + WS_VALUE_TEXT +}; + +struct WSText { + char *str; + size_t length; +}; + +struct WSXmlData { + WebdavNSList *namespaces; + char *data; + size_t length; +}; + +struct WebdavProperty { + WSNamespace *namespace; + + const char *name; + + char *lang; + + union { + WSXmlNode *node; + WSXmlData data; + WSText text; + } value; + WebdavValueType vtype; +}; + +struct WebdavPList { + WebdavProperty *property; + WebdavPList *prev; + WebdavPList *next; +}; + +struct WebdavNSList { + WSNamespace *namespace; + WebdavNSList *prev; + WebdavNSList *next; +}; + +struct WebdavPListIterator { + WebdavPList **list; + WebdavPList *cur; + WebdavPList *next; + size_t index; +}; + +enum WebdavLockScope { + WEBDAV_LOCK_EXCLUSIVE = 0, + WEBDAV_LOCK_SHARED, + WEBDAV_LOCK_SCOPE_UNKNOWN +}; + +enum WebdavLockType { + WEBDAV_LOCK_WRITE = 0, + WEBDAV_LOCK_TYPE_UNKNOWN +}; + +struct WebdavPropfindRequest { + Session *sn; + Request *rq; + + WebdavBackend *dav; + + void *doc; + + /* + * list of requested properties + */ + WebdavPList *properties; + + /* + * number of properties + */ + size_t propcount; + + WSBool allprop; + WSBool propname; + WSBool deadproperties; + + int depth; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavProppatchRequest { + Session *sn; + Request *rq; + + WebdavBackend *dav; + + void *doc; + + WebdavPList *set; + size_t setcount; + + WebdavPList *remove; + size_t removecount; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavVFSRequest { + Session *sn; + Request *rq; + + char *path; + + /* + * custom userdata for the backend + */ + void *userdata; +}; + +struct WebdavLockRequest { + Session *sn; + Request *rq; + + void *doc; + + WebdavLockScope scope; + WebdavLockType type; + + WSXmlNode *owner; +}; + +struct WebdavVFSProperties { + uint32_t getcontentlength:1; + uint32_t getlastmodified:1; + uint32_t getresourcetype:1; + uint32_t getetag:1; + uint32_t creationdate:1; +}; + +struct WebdavResponse { + WebdavOperation *op; + + WebdavResource* (*addresource)(WebdavResponse*, const char*); +}; + +struct WebdavResource { + char *href; + + WSBool isclosed; + + int err; + + /* + * int addprop(WebdavResource *res, WebdavProperty *property, int status); + * + * Adds a property to the resource + */ + int (*addproperty)(WebdavResource*, WebdavProperty*, int); + + /* + * int close(WebdavResource *res); + * + * Closes a resource object + */ + int (*close)(WebdavResource*); +}; + +struct WebdavBackend { + /* + * int propfind_init( + * WebdavPropfindRequest *rq, + * const char *path, + * const char *href, + * WebdavPList **outplist); + * + * Initializes a propfind request. This is called once for each propfind + * request and should initialize everything needed for generating the + * multistatus response. + * + */ + int (*propfind_init)(WebdavPropfindRequest *, const char *, const char *, WebdavPList **); + + /* + * int propfind_do( + * WebdavPropfindRequest *rq, + * WebdavResponse *response, + * VFS_DIR parent, + * WebdavResource *resource, + * struct stat *s); + * + * This function is called for the requsted resource and for all children + * if WS_PROPFIND_NO_VFS_CHILDREN is not set. + */ + int (*propfind_do)( + WebdavPropfindRequest *, + WebdavResponse *, + VFS_DIR, + WebdavResource *, + struct stat *); + + /* + * int propfind_finish(WebdavPropfindRequest *rq); + * + * Finishes a propfind request. + */ + int (*propfind_finish)(WebdavPropfindRequest *); + + /* + * int proppatch_do( + * WebdavProppatchRequest *request, + * WebdavResource *response, + * VFSFile *file, + * WebdavPList **out_set, + * WebdavPList **out_remove); + * + * Modifies properties of the requsted resource. + */ + int (*proppatch_do)( + WebdavProppatchRequest *, + WebdavResource *, + VFSFile *, + WebdavPList **, + WebdavPList **); + + /* + * int proppatch_finish( + * WebdavProppatchRequest *request, + * WebdavResource *response, + * VFSFile *file, + * WSBool commit); + * + * Called after all proppatch_do functions of all backends are executed + * and should either permanently store the properties (commit == true) or + * revert all changed (commit == false). + */ + int (*proppatch_finish)( + WebdavProppatchRequest *, + WebdavResource *, + VFSFile *, + WSBool); + + /* + * int opt_mkcol(WebdavVFSRequest *request, WSBool *out_created); + * + * Optional mkcol callback that is called before vfs_mkdir. If the function + * sets out_created to TRUE, vfs_mkdir will not be executed. + */ + int (*opt_mkcol)(WebdavVFSRequest *, WSBool *); + + /* + * int opt_mkcol_finish(WebdavVFSRequest *request, WSBool success); + * + * Optional callback for finishing a MKCOL request. + */ + int(*opt_mkcol_finish)(WebdavVFSRequest *, WSBool); + + /* + * int opt_delete(WebdavVFSRequest *request, WSBool *out_deleted); + * + * Optional delete callback that is called once before any VFS deletions. + * When the callback sets out_deleted to TRUE, no VFS unlink operations + * will be done. + * + */ + int (*opt_delete)(WebdavVFSRequest *, WSBool *); + + /* + * int opt_delete_finish(WebdavVFSRequest *request, WSBool success); + * + * Optional callback for finishing a DELETE request. + */ + int (*opt_delete_finish)(WebdavVFSRequest *, WSBool); + + /* + * See the WS_WEBDAV_* macros for informations about the settings + */ + uint32_t settings; + + /* + * private instance data + */ + void *instance; + + /* + * next Backend + */ + WebdavBackend *next; +}; + +/* + * register a webdav backend + */ +int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate); + +WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData); + +/* + * gets the requested depth + * + * in case of infinity, -1 is returned + * if no depth is specified, 0 is returned + */ +int webdav_getdepth(Request *rq); + +int webdav_plist_add( + pool_handle_t *pool, + WebdavPList **begin, + WebdavPList **end, + WebdavProperty *prop); + +WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list); +WebdavPList* webdav_plist_clone_s( + pool_handle_t *pool, + WebdavPList *list, + size_t *newlen); + +size_t webdav_plist_size(WebdavPList *list); + +int webdav_nslist_add( + pool_handle_t *pool, + WebdavNSList **begin, + WebdavNSList **end, + WSNamespace *ns); + +WebdavPListIterator webdav_plist_iterator(WebdavPList **list); +int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur); +void webdav_plist_iterator_remove_current(WebdavPListIterator *i); + +WSNamespace* webdav_dav_namespace(void); +WebdavProperty* webdav_resourcetype_collection(void); +WebdavProperty* webdav_resourcetype_empty(void); +WebdavProperty* webdav_dav_property( + pool_handle_t *pool, + const char *name); + +int webdav_resource_add_dav_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *name, + const char *str, + size_t len); +int webdav_resource_add_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *xmlns_prefix, + const char *xmlns_href, + const char *name, + const char *str, + size_t len); + +int webdav_property_set_value( + WebdavProperty *property, + pool_handle_t *pool, + char *value); + +WebdavVFSProperties webdav_vfs_properties( + WebdavPList **plistInOut, + WSBool removefromlist, + WSBool appprop, + uint32_t flags); + +int webdav_add_vfs_properties( + WebdavResource *res, + pool_handle_t *pool, + WebdavVFSProperties properties, + struct stat *s); + +int wsxml_iterator( + pool_handle_t *pool, + WSXmlNode *node, + wsxml_func begincb, + wsxml_func endcb, + void *udata); + +WebdavNSList* wsxml_get_required_namespaces( + pool_handle_t *pool, + WSXmlNode *node, + int *error); + +WSXmlData* wsxml_node2data( + pool_handle_t *pool, + WSXmlNode *node); + +/* + * converts a property list to a string + * + * namespaces are separated by a newline character and have the format: + * : + */ +char* wsxml_nslist2string(pool_handle_t *pool, WebdavNSList *nslist); + +/* + * converts a namespace list string (created by wsxml_nslist2string) to + * a WebdavNSList object + */ +WebdavNSList* wsxml_string2nslist(pool_handle_t *pool, char *nsliststr); #ifdef __cplusplus } diff -r 21274e5950af -r a1f4cb076d2f src/server/safs/cgi.c --- a/src/server/safs/cgi.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/safs/cgi.c Sat Sep 24 16:26:10 2022 +0200 @@ -36,9 +36,10 @@ #include #include +#include + #include "../util/util.h" #include "../util/pblock.h" -#include "../../ucx/string.h" #include "../daemon/netsite.h" #include "../util/io.h" @@ -190,7 +191,12 @@ log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path); kill(cgip.pid, SIGKILL); } - cgi_close(&cgip); // TODO: check return value + + int exit_code = cgi_close(&cgip); + if(exit_code != 0) { + log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", path, exit_code); + ret = REQ_ABORTED; + } cgi_parser_free(parser); return result; @@ -268,7 +274,7 @@ system_close(p->out[1]); } - return 0; + return status; } CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) { diff -r 21274e5950af -r a1f4cb076d2f src/server/safs/cgi.h --- a/src/server/safs/cgi.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/safs/cgi.h Sat Sep 24 16:26:10 2022 +0200 @@ -30,7 +30,7 @@ #define CGI_H #include "../public/nsapi.h" -#include "../../ucx/buffer.h" +#include #ifdef __cplusplus extern "C" { diff -r 21274e5950af -r a1f4cb076d2f src/server/safs/common.c --- a/src/server/safs/common.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/safs/common.c Sat Sep 24 16:26:10 2022 +0200 @@ -33,7 +33,7 @@ #include "../util/pblock.h" #include "../util/util.h" -#include "../../ucx/map.h" +#include static UcxMap *var_names; diff -r 21274e5950af -r a1f4cb076d2f src/server/safs/nametrans.c --- a/src/server/safs/nametrans.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/safs/nametrans.c Sat Sep 24 16:26:10 2022 +0200 @@ -32,6 +32,80 @@ #include "../daemon/request.h" #include "../util/pblock.h" #include "../util/util.h" +#include "../public/webdav.h" + +#include "../daemon/session.h" +#include "../daemon/config.h" + +#include "../public/vfs.h" + +static int initialize_dav_repo(pblock *pb, Session *sn, Request *rq, WebdavRepository *repo) { + if(repo->vfs) { + VFS *vfs = repo->vfs->create(sn, rq, pb, repo->vfsInitData); + if(!vfs) { + return REQ_ABORTED; + } + rq->vfs = vfs; + } + + WebdavBackend *backend_first = NULL; + WebdavBackend *backend_last = NULL; + UCX_FOREACH(elm, repo->davBackends) { + WebdavBackendInitData *davInit = elm->data; + WebdavBackend *backend = davInit->davType->create(sn, rq, pb, davInit->davInitData); + if(!backend) { + return REQ_ABORTED; + } + + if(backend_last) { + backend_last->next = backend; + backend_last = backend; + } else { + backend_first = backend; + backend_last = backend; + } + } + rq->davCollection = backend_first; + + return 0; +} + +static int nametrans_set_dav_repository(pblock *pb, Session *sn, Request *rq) { + char *dav = pblock_findkeyval(pb_key_dav, pb); + if(!dav) return 0; + + ServerConfiguration *config = session_get_config(sn); + WebdavRepository *repo = ucx_map_cstr_get(config->dav, dav); + + if(!repo) { + log_ereport(LOG_MISCONFIG, "nametrans: unknown dav repository '%s'", dav); + return REQ_ABORTED; + } + + return initialize_dav_repo(pb, sn, rq, repo); +} + +static int nametrans_set_vfs(pblock *pb, Session *sn, Request *rq) { + char *vfsclass = pblock_findkeyval(pb_key_vfsclass, pb); + if(!vfsclass) return 0; + + VFS *vfs = vfs_create(sn, rq, vfsclass, pb, NULL); + if(!vfs) { + return 1; + } + rq->vfs = vfs; + return 0; +} + +static int nametrans_set_dav(pblock *pb, Session *sn, Request *rq) { + char *davclass = pblock_findkeyval(pb_key_davclass, pb); + if(!davclass) return 0; + + WebdavBackend *dav = webdav_create(sn, rq, davclass, pb, NULL); + + rq->davCollection = dav; + return 0; +} /* * assign_name @@ -66,6 +140,19 @@ i++; } } + + if(nametrans_set_dav_repository(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "assign-name: cannot create dav repository: name=%s from=%s", name, from); + return REQ_ABORTED; + } + if(nametrans_set_vfs(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "assign-name: cannot create VFS: name=%s from=%s", name, from); + return REQ_ABORTED; + } + if(nametrans_set_dav(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "assign-name: cannot create Webdav Backend: name=%s from=%s", name, from); + return REQ_ABORTED; + } // add object to rq->vars pblock_kvinsert(pb_key_name, name, strlen(name), rq->vars); @@ -89,6 +176,11 @@ return REQ_ABORTED; } + if(nametrans_set_vfs(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "document-root: cannot create VFS"); + return REQ_ABORTED; + } + sstr_t root_str = sstr(root); sstr_t uri_str = sstr(pblock_findkeyval(pb_key_uri, rq->reqpb)); @@ -106,6 +198,9 @@ * from prefix * dir file system directory * name (optional) object name + * dav (optional) dav repository name + * vfsclass (optional) vfs name + * davclass (optional) dav backend * */ int pfx2dir(pblock *pb, Session *sn, Request *rq) { @@ -147,6 +242,19 @@ uri++; } + if(nametrans_set_dav_repository(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "pfx2dir: cannot create dav repository: from=%s dir=%s name=%s", from, dir, name); + return REQ_ABORTED; + } + if(nametrans_set_vfs(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "pfx2dir: cannot create VFS: from=%s dir=%s name=%s", from, dir, name); + return REQ_ABORTED; + } + if(nametrans_set_dav(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "pfx2dir: cannot create Webdav Backend: from=%s dir=%s name=%s", from, dir, name); + return REQ_ABORTED; + } + request_set_path(sstr(dir), sstr(uri), rq->vars); if(name) { @@ -192,6 +300,11 @@ return REQ_ABORTED; } + if(nametrans_set_vfs(pb, sn, rq)) { + log_ereport(LOG_FAILURE, "simple-rewrite: cannot create VFS: from=%s root=%s path=%s name=%s", from, root, path, name); + return REQ_ABORTED; + } + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); sstr_t u = sstr(uri); sstr_t f = sstr(from); diff -r 21274e5950af -r a1f4cb076d2f src/server/safs/service.c --- a/src/server/safs/service.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/safs/service.c Sat Sep 24 16:26:10 2022 +0200 @@ -85,7 +85,8 @@ } // sets last-modified, content-length and checks conditions - if(http_set_finfo(sn, rq, s) != REQ_PROCEED) { + const char *etag = vfs_getetag(fd); // optionally, get etag from file + if(http_set_finfo_etag(sn, rq, s, etag) != REQ_PROCEED) { vfs_close(fd); return NULL; } @@ -637,7 +638,7 @@ // send response header http_start_response(sn, rq); // send content - // TODO: fix: send_range_aio is unstable + // TODO: fix: send_range_aio is unstable #96 //ret = send_range_aio(sn, rq, fd, offset, length, NULL, 0); //if(ret == REQ_PROCESSING) { // return ret; diff -r 21274e5950af -r a1f4cb076d2f src/server/test/Makefile --- a/src/server/test/Makefile Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/test/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -29,8 +29,8 @@ TEST_CFLAGS = $(TEST_OBJPRE)%.o: test/%.c - $(CC) -o $@ -c $(ADMIN_CFLAGS) $(CFLAGS) $< + $(CC) -o $@ -c $(TEST_CFLAGS) $(CFLAGS) $< $(TEST_OBJPRE)%.o: test/%.cpp - $(CXX) -o $@ -c $(ADMIN_CFLAGS) $(CFLAGS) $< + $(CXX) -o $@ -c $(TEST_CFLAGS) $(CFLAGS) $< diff -r 21274e5950af -r a1f4cb076d2f src/server/test/main.c --- a/src/server/test/main.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/test/main.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,11 +38,18 @@ #include "../public/nsapi.h" #include "../util/plist.h" #include "../util/date.h" +#include "../daemon/vfs.h" -#include "../../ucx/string.h" +#include -static int std_pipe_fds[2]; -static WSBool is_daemon; +#include "vfs.h" +#include "writer.h" +#include "xml.h" +#include "webdav.h" +#include "uri.h" + +void register_pg_tests(int argc, char **argv, UcxTestSuite *suite); + void test() { @@ -50,16 +57,78 @@ // needed for linking WSBool main_is_daemon(void) { - return is_daemon; + return 0; } int main(int argc, char **argv) { pool_init(NULL, NULL, NULL); + vfs_init(); //test(); printf("%s", "Webserver Test Suite\n====================\n\n"); - + + UcxTestSuite* suite = ucx_test_suite_new(); + + // util tests + ucx_test_register(suite, test_util_uri_escape_alphanum); + ucx_test_register(suite, test_util_uri_escape_space); + ucx_test_register(suite, test_util_uri_escape_latin); + ucx_test_register(suite, test_util_uri_escape_kanji); + + // vfs tests + ucx_test_register(suite, test_vfs_open); + ucx_test_register(suite, test_vfs_mkdir); + ucx_test_register(suite, test_vfs_opendir); + ucx_test_register(suite, test_vfs_readdir); + ucx_test_register(suite, test_vfs_unlink); + ucx_test_register(suite, test_vfs_rmdir); + + // writer tests + ucx_test_register(suite, test_writer_putc); + ucx_test_register(suite, test_writer_flush); + ucx_test_register(suite, test_writer_put); + + // xml tests + ucx_test_register(suite, test_wsxml_iterator); + ucx_test_register(suite, test_wsxml_get_required_namespaces); + ucx_test_register(suite, test_wsxml_write_nodes); + ucx_test_register(suite, test_wsxml_nslist2string); + ucx_test_register(suite, test_wsxml_string2nslist); + + // webdav tests + ucx_test_register(suite, test_webdav_plist_add); + ucx_test_register(suite, test_webdav_plist_size); + ucx_test_register(suite, test_propfind_parse); + ucx_test_register(suite, test_proppatch_parse); + ucx_test_register(suite, test_lock_parse); + ucx_test_register(suite, test_rqbody2buffer); + ucx_test_register(suite, test_webdav_plist_iterator); + ucx_test_register(suite, test_webdav_plist_iterator_remove_current); + ucx_test_register(suite, test_msresponse_addproperty); + ucx_test_register(suite, test_webdav_propfind_init); + ucx_test_register(suite, test_webdav_op_propfind_begin); + ucx_test_register(suite, test_webdav_op_propfind_children); + ucx_test_register(suite, test_proppatch_msresponse); + ucx_test_register(suite, test_msresponse_addproperty_with_errors); + ucx_test_register(suite, test_webdav_op_proppatch); + ucx_test_register(suite, test_webdav_vfs_op_do); + ucx_test_register(suite, test_webdav_delete); + + // webdav methods + ucx_test_register(suite, test_webdav_propfind); + ucx_test_register(suite, test_webdav_proppatch); + ucx_test_register(suite, test_webdav_put); + + // plugin tests +#ifdef ENABLE_POSTGRESQL + register_pg_tests(argc, argv, suite); +#endif + + // run tests + ucx_test_run(suite, stdout); + fflush(stdout); + return EXIT_SUCCESS; } diff -r 21274e5950af -r a1f4cb076d2f src/server/test/objs.mk --- a/src/server/test/objs.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/test/objs.mk Sat Sep 24 16:26:10 2022 +0200 @@ -31,6 +31,12 @@ TEST_OBJPRE = $(OBJ_DIR)$(TEST_SRC_DIR) TESTOBJ = main.o +TESTOBJ += testutils.o +TESTOBJ += webdav.o +TESTOBJ += vfs.o +TESTOBJ += xml.o +TESTOBJ += writer.o +TESTOBJ += uri.o TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%) TESTSOURCE = $(TESTOBJ:%.o=test/%.c) diff -r 21274e5950af -r a1f4cb076d2f src/server/test/testutils.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/testutils.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,171 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include + +#include "../util/pblock.h" + +#include "../util/io.h" + +#include "testutils.h" + +Session* testutil_session(void) { + pool_handle_t *pool = pool_create(); + NSAPISession *sn = nsapisession_create(pool); + sn->connection = testutil_dummy_connection(pool); + + return &sn->sn; +} + +Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri) { + NSAPIRequest *rq = pool_malloc(pool, sizeof(NSAPIRequest)); + ZERO(rq, sizeof(NSAPIRequest)); + + HTTPRequest httprequest; + ZERO(&httprequest, sizeof(HTTPRequest)); + request_initialize(pool, &httprequest, rq); + + sstr_t clf = ucx_sprintf("%s %s HTTP/1.1", method, uri); + pblock_kvinsert( + pb_key_clf_request, + clf.ptr, + clf.length, + rq->rq.reqpb); + free(clf.ptr); + + pblock_nvinsert( + "method", + method, + rq->rq.reqpb); + + pblock_nvinsert( + "protocol", + "HTTP/1.1", + rq->rq.reqpb); + + pblock_nvinsert("uri", uri, rq->rq.reqpb); + + return &rq->rq; +} + +static int dummyconn_read(Connection *conn, void *buf, int len) { + return len; +} + +static int dummyconn_write(Connection *conn, const void *buf, int len) { + return len; +} + +static void dummyconn_close(Connection *conn) { + +} + + +Connection* testutil_dummy_connection(pool_handle_t *pool) { + Connection *conn = pool_malloc(pool, sizeof(Connection)); + ZERO(conn, sizeof(Connection)); + conn->read = dummyconn_read; + conn->write = dummyconn_write; + conn->close = dummyconn_close; + return conn; +} + +void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len) { + sstr_t cl = ucx_sprintf("%d", (int)len); + pblock_nvreplace("content-length", cl.ptr, rq->headers); + free(cl.ptr); + + netbuf *inbuf = pool_malloc(sn->pool, sizeof(netbuf)); + inbuf->sd = NULL; + inbuf->inbuf = pool_malloc(sn->pool, len); + inbuf->pos = 0; + inbuf->maxsize = len; + inbuf->cursize = len; + sn->inbuf = inbuf; + + memcpy(inbuf->inbuf, body, len); +} + +void testutil_destroy_session(Session *sn) { + pool_destroy(sn->pool); +} + + +static ssize_t test_io_write(IOStream *io, void *buf, size_t size) { + TestIOStream *st = (TestIOStream*)io; + return ucx_buffer_write(buf, 1, size, st->buf); +} + +static ssize_t test_io_writev(IOStream *io, struct iovec *iovec, int iovctn) { + return -1; +} + +static ssize_t test_io_read(IOStream *io, void *buf, size_t size) { + return -1; +} + +static void test_io_close(IOStream *io) { + +} + +static void test_io_finish(IOStream *io) { + +} + +static void test_io_setmode(IOStream *io, int mode) { + +} + +static int test_io_poll(IOStream *io, EventHandler *ev, int events , Event *event) { + return 1; +} + +TestIOStream* testutil_iostream(size_t size, int autoextend) { + TestIOStream *stream = calloc(1, sizeof(TestIOStream)); + int flags = 0; + if(autoextend) { + flags = UCX_BUFFER_AUTOEXTEND; + } + stream->buf = ucx_buffer_new(NULL, size, flags); + + stream->io.st.write = test_io_write; + stream->io.st.writev = test_io_writev; + stream->io.st.close = test_io_close; + stream->io.st.finish = test_io_finish; + + return stream; +} + +void testutil_iostream_destroy(TestIOStream *stream) { + ucx_buffer_free(stream->buf); + free(stream); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/test/testutils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/testutils.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,66 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include "../public/nsapi.h" +#include "../daemon/httprequest.h" + +#include "../util/io.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TestIOStream { + HttpStream io; + UcxBuffer *buf; +} TestIOStream; + +Session* testutil_session(void); + +Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri); + +Connection* testutil_dummy_connection(pool_handle_t *pool); + +void testutil_request_body(Session *sn, Request *rq, const char *body, size_t len); + +void testutil_destroy_session(Session *sn); + +TestIOStream* testutil_iostream(size_t size, int autoextend); +void testutil_iostream_destroy(TestIOStream *stream); + + +#ifdef __cplusplus +} +#endif + +#endif /* TESTUTILS_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/uri.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/uri.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,108 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "uri.h" + +#include "../util/util.h" + + +UCX_TEST(test_util_uri_escape_alphanum) { + char *str1 = "/test/path/abc/"; + char str_enc[512]; + + UCX_TEST_BEGIN; + + char *test = util_uri_escape(str_enc, str1); + UCX_TEST_ASSERT(test, "util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str1), "test != str1"); + + UCX_TEST_END; +} + +UCX_TEST(test_util_uri_escape_space) { + char *str1 = "/test/space in path/"; + char *str_enc_expected = "/test/space%20in%20path/"; + char str_enc[512]; + + UCX_TEST_BEGIN; + + char *test = util_uri_escape(str_enc, str1); + UCX_TEST_ASSERT(test, "util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str_enc_expected), "unexpected result"); + + UCX_TEST_END; +} + +UCX_TEST(test_util_uri_escape_latin) { + char *str1 = "/test/path/öäütestß/"; + char *str_enc_expected = "/test/path/%C3%B6%C3%A4%C3%BCtest%C3%9F/"; + + char *str2 = "€"; + char *str2_enc_expected = "%E2%82%AC"; + + char str_enc[512]; + + UCX_TEST_BEGIN; + + // test 1 + char *test = util_uri_escape(str_enc, str1); + UCX_TEST_ASSERT(test, "util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str_enc_expected), "unexpected result"); + + // test 2 + test = util_uri_escape(str_enc, str2); + UCX_TEST_ASSERT(test, "(2) util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str2_enc_expected), "(2) unexpected result"); + + UCX_TEST_END; +} + +UCX_TEST(test_util_uri_escape_kanji) { + char *str1 = "漢字"; + char *str1_enc_expected = "%E6%BC%A2%E5%AD%97"; + + char *str2 = "/test/エンコーディング/漢字/"; + char *str2_enc_expected = "/test/%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0/%E6%BC%A2%E5%AD%97/"; + + char str_enc[512]; + + UCX_TEST_BEGIN; + + // test 1 + char *test = util_uri_escape(str_enc, str1); + UCX_TEST_ASSERT(test, "util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str1_enc_expected), "unexpected result"); + + // test 2 + test = util_uri_escape(str_enc, str2); + UCX_TEST_ASSERT(test, "(2) util_uri_escape returned NULL"); + UCX_TEST_ASSERT(!strcasecmp(test, str2_enc_expected), "(2) unexpected result"); + + UCX_TEST_END; +} + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/uri.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/uri.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef TEST_URI_H +#define TEST_URI_H + +#include "../public/nsapi.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_util_uri_escape_alphanum); +UCX_TEST(test_util_uri_escape_space); +UCX_TEST(test_util_uri_escape_latin); +UCX_TEST(test_util_uri_escape_kanji); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_URI_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/vfs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/vfs.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,558 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include + +#include "../daemon/session.h" + +#include "testutils.h" + +#include "vfs.h" + +typedef struct TestVFS { + UcxMap *files; + int count_unlink; + int count_rmdir; +} TestVFS; + +typedef struct TestVFSFile { + VFSFile file; + sstr_t path; + int isdir; + UcxBuffer *content; +} TestVFSFile; + +typedef struct TestVFSDir { + VFSDir dir; + TestVFSFile *file; + UcxMapIterator i; + sstr_t name; +} TestVFSDir; + +/* dir io */ + +static char* test_resource_name(char *url) { + sstr_t urlstr = sstr(url); + if(urlstr.ptr[urlstr.length-1] == '/') { + urlstr.length--; + } + sstr_t resname = sstrrchr(urlstr, '/'); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { + TestVFS *vfs = dir->ctx->vfs->instance; + TestVFSDir *vfsdir = (TestVFSDir*)dir; + + sstr_t prefix = sstrcat(2, vfsdir->file->path, S("/")); + + TestVFSFile *file = NULL; + UCX_MAP_FOREACH(key, file, vfsdir->i) { + sstr_t file_path = sstrcat( + 2, + prefix, + sstr(test_resource_name(file->path.ptr))); + void *m = ucx_map_get(vfs->files, ucx_key(file_path.ptr, file_path.length)); + // don't ask why alfree and not free() + alfree(ucx_default_allocator(), file_path.ptr); + if(m) { + break; + } else { + file = NULL; + } + } + free(prefix.ptr); + + if(file) { + vfsdir->name = sstrdup_a( + session_get_allocator(dir->ctx->sn), + sstr(test_resource_name(file->path.ptr))); + ZERO(entry, sizeof(VFS_ENTRY)); + entry->name = vfsdir->name.ptr; + + if(getstat) { + ZERO(&entry->stat, sizeof(struct stat)); + if(file->isdir) { + entry->stat.st_mode = S_IFDIR; + } + } + + return 1; + } else { + return 0; + } +} + +void testvfs_dir_close(VFS_DIR dir) { + TestVFSDir *testdir = (TestVFSDir*)dir; + pool_free(testdir->dir.ctx->sn->pool, dir); + +} + + ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_read(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_write(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_read(fd, buf, nbyte); + } + + ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_write(fd, buf, nbyte); + } + + off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) { + TestVFSFile *file = (TestVFSFile*)fd; + ucx_buffer_seek(file->content, offset, whence); + return (off_t)file->content->pos; + } + + void testvfs_close(SYS_FILE fd) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = 0; + } + +VFS_IO test_file_io = { + testvfs_read, + testvfs_write, + testvfs_pread, + testvfs_pwrite, + testvfs_seek, + testvfs_close, + NULL, /* aio_read */ + NULL /* aio_write */ +}; + +VFS_DIRIO test_dir_io = { + testvfs_readdir, + testvfs_dir_close +}; + + +/* vfs funcs */ + +SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + if((oflags & O_CREAT) == O_CREAT) { + file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile)); + ZERO(file, sizeof(TestVFSFile)); + file->file.ctx = ctx; + file->path = sstrdup_a(session_get_allocator(ctx->sn), s_path); + file->file.io = &test_file_io; + + file->content = pool_calloc(ctx->sn->pool, 1, sizeof(UcxBuffer)); + file->content->capacity = 2048; + file->content->space = pool_malloc(ctx->sn->pool, file->content->capacity); + file->content->flags = 0; + file->content->pos = 0; + file->content->size = 0; + + ucx_map_sstr_put(vfs->files, s_path, file); + } else { + ctx->vfs_errno = ENOENT; + } + } + + return (SYS_FILE)file; +} + +int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return 1; + } + + ZERO(buf, sizeof(struct stat)); + if(file->isdir) { + buf->st_mode = S_IFDIR; + } + + return 0; +} + +int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { + return 0; +} + +VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return NULL; + } + + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = (TestVFSFile*)fd; + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +int testvfs_mkdir(VFSContext *ctx, const char *path) { + SYS_FILE fd = testvfs_open(ctx, path, O_CREAT); + if(!fd) { + return 1; + } + + TestVFSFile *file = (TestVFSFile*)fd; + file->isdir = 1; + + return 0; +} + +int testvfs_unlink(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = ucx_map_cstr_get(vfs->files, path); + if(!file) { + return 1; + } + + if(file->isdir) { + return 1; + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_unlink++; + return 0; +} + +int testvfs_rmdir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *dir = ucx_map_cstr_get(vfs->files, path); + if(!dir) { + ctx->vfs_errno = ENOENT; + return 1; + } + + if(!dir->isdir) { + return 1; + } + + UcxMapIterator i = ucx_map_iterator(vfs->files); + TestVFSFile *f; + UCX_MAP_FOREACH(key, f, i) { + if(f->path.length > dir->path.length && sstrprefix(f->path, dir->path)){ + return 1; // dir not empty + } + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_rmdir++; + return 0; +} + +static VFS testVFSClass = { + testvfs_open, + testvfs_stat, + testvfs_fstat, + testvfs_opendir, + testvfs_fdopendir, + testvfs_mkdir, + testvfs_unlink, + testvfs_rmdir, + 0, + NULL +}; + + +VFS* testvfs_create(Session *sn) { + TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS)); + vfs->count_unlink = 0; + vfs->count_rmdir = 0; + vfs->files = ucx_map_new_a(session_get_allocator(sn), 64); + + testVFSClass.instance = vfs; + return &testVFSClass; +} + + +/* ------------------------------------------------------------------------- */ +// +// VFS Tests +// +/* ------------------------------------------------------------------------- */ + +UCX_TEST(test_vfs_open) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(vfs, "vfs is NULL"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file1", 0); + UCX_TEST_ASSERT(f2, "f2 not opened"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_mkdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + SYS_FILE fd = vfs_open(vfs, "/dir", 0); + UCX_TEST_ASSERT(fd, "no fd"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_opendir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "no dir"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_readdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + // add some test file to /dir + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "dir not opened"); + + UcxMap *files = ucx_map_new(8); + + VFSEntry entry; + while(vfs_readdir(dir, &entry)) { + ucx_map_cstr_put(files, entry.name, dir); + } + + UCX_TEST_ASSERT(files->count == 4, "wrong files count"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file1"), "file1 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file2"), "file2 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file3"), "file3 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file4"), "file4 missing"); + + ucx_map_free(files); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_unlink) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT); + UCX_TEST_ASSERT(f1, "f2 not opened"); + + SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT); + UCX_TEST_ASSERT(f1, "f3 not opened"); + + // test unlink + err = vfs_unlink(vfs, "/file1"); + UCX_TEST_ASSERT(err == 0, "unlink /file1 failed"); + err = vfs_unlink(vfs, "/dir1/file3"); + UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed"); + + err = vfs_unlink(vfs, "/filex"); + UCX_TEST_ASSERT(err != 0, "unlink /filex should fail"); + + // check if files were removed + SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted"); + SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted"); + + // file2 should still be there + SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY); + UCX_TEST_ASSERT(o2, "/file2 deleted"); + + // check if dir unlink fails + err = vfs_unlink(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_rmdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail"); + err = vfs_rmdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed"); + + err = vfs_unlink(vfs, "/dir1/file1"); + UCX_TEST_ASSERT(err == 0, "unlink failed"); + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/test/vfs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/vfs.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_VFS_H +#define TEST_VFS_H + +#include "../public/nsapi.h" +#include "../public/vfs.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +VFS* testvfs_create(Session *sn); + +UCX_TEST(test_vfs_open); +UCX_TEST(test_vfs_mkdir); +UCX_TEST(test_vfs_opendir); +UCX_TEST(test_vfs_readdir); +UCX_TEST(test_vfs_unlink); +UCX_TEST(test_vfs_rmdir); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_VFS_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/webdav.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/webdav.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,1767 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "testutils.h" + +#include "../webdav/requestparser.h" +#include "../webdav/webdav.h" +#include "../webdav/multistatus.h" +#include "../webdav/operation.h" + +#include "vfs.h" +#include "webdav.h" + +static int webdav_is_initialized = 0; + +/* ----------------------------- Test Backends --------------------------*/ + +static int backend2_init_called = 0; +static int backend2_propfind_do_count = 0; +static int backend2_propfind_finish_called = 0; +static int backend2_proppatch_commit = 0; +static int backend2_proppatch_do_count = 0; +static int backend2_proppatch_finish_count = 0; + +// backend2 +static int backend2_propfind_init( + WebdavPropfindRequest *propfind, + const char *path, + const char *href, + WebdavPList **outPList) +{ + backend2_init_called = 1; + return 0; +} + +static int backend2_propfind_do( + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + backend2_propfind_do_count++; + return 0; +} + +static int backend2_propfind_finish(WebdavPropfindRequest *propfind) { + backend2_propfind_finish_called = 1; + return 0; +} + +static int backend2_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove) +{ + backend2_proppatch_do_count++; + + if(*out_remove) { + return 1; // backend1 should remove all remove-props + } + + WebdavPListIterator i = webdav_plist_iterator(out_set); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + if(!strcmp(cur->property->name, "a")) { + // property 'a' should already be removed by backend1 + return 1; + } else if(!strcmp(cur->property->name, "abort")) { + return 1; // test abort + } + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + + return 0; +} + +static int backend2_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + backend2_proppatch_finish_count++; + backend2_proppatch_commit = commit; + return 0; +} + +static WebdavBackend backend2 = { + backend2_propfind_init, + backend2_propfind_do, + backend2_propfind_finish, + backend2_proppatch_do, + backend2_proppatch_finish, + NULL, // opt_mkcol + NULL, // opt_mkcol_finish + NULL, // opt_delete + NULL, // opt_delete_finish + 0, + NULL, // instance + NULL +}; + +// backend1 + +static int backend1_init_called = 0; +static int backend1_propfind_do_count = 0; +static int backend1_propfind_finish_called = 0; +static int backend1_proppatch_commit = 0; +static int backend1_proppatch_do_count = 0; +static int backend1_proppatch_finish_count = 0; + + +static int backend1_propfind_init( + WebdavPropfindRequest *propfind, + const char *path, + const char *href, + WebdavPList **outPList) +{ + backend1_init_called = 1; + + WebdavPList *plist = *outPList; + WebdavProperty *p = plist->property; + if(!strcmp(p->name, "displayname")) { + plist->next->prev = NULL; + *outPList = plist->next; // remove first item from plist + } else { + return 1; + } + + return 0; +} + +static int backend1_propfind_do( + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + backend1_propfind_do_count++; + return 0; +} + +static int backend1_propfind_finish(WebdavPropfindRequest *propfind) { + backend1_propfind_finish_called = 1; + return 0; +} + +static int backend1_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **out_set, + WebdavPList **out_remove) +{ + backend1_proppatch_do_count++; + + // remove everything from out_remove + WebdavPListIterator i = webdav_plist_iterator(out_remove); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + + // remove property 'a' and fail at property 'fail' + i = webdav_plist_iterator(out_set); + while(webdav_plist_iterator_next(&i, &cur)) { + if(!strcmp(cur->property->name, "fail")) { + response->addproperty(response, cur->property, 403); + webdav_plist_iterator_remove_current(&i); + } else if(!strcmp(cur->property->name, "a")) { + response->addproperty(response, cur->property, 200); + webdav_plist_iterator_remove_current(&i); + } + } + + return 0; +} + +static int backend1_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + backend1_proppatch_finish_count++; + backend1_proppatch_commit = commit; + return 0; +} + +WebdavBackend backend1 = { + backend1_propfind_init, + backend1_propfind_do, + backend1_propfind_finish, + backend1_proppatch_do, + backend1_proppatch_finish, + NULL, // opt_mkcol + NULL, // opt_mkcol_finish + NULL, // opt_delete + NULL, // opt_delete_finish + 0, + NULL, // instance + &backend2 +}; + +static void reset_backends(void) { + backend1_init_called = 0; + backend1_propfind_do_count = 0; + backend1_propfind_finish_called = 0; + backend1_proppatch_commit = 0; + backend1_proppatch_do_count = 0; + backend1_proppatch_finish_count = 0; + backend2_init_called = 0; + backend2_propfind_do_count = 0; + backend2_propfind_finish_called = 0; + backend2_proppatch_commit = 0; + backend2_proppatch_do_count = 0; + backend2_proppatch_finish_count = 0; +} + +/* ----------------------------------------------------------------------*/ + + +static int test_init( + Session **out_sn, + Request **out_rq, + WebdavPropfindRequest **out_propfind, + const char *xml) +{ + if(!webdav_is_initialized) { + if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) { + return 1; + } + webdav_is_initialized = 1; + } + + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPFIND", "/"); + + int error = 0; + + WebdavPropfindRequest *propfind = propfind_parse( + sn, + rq, + xml, + strlen(xml), + &error); + + if(error) { + return 1; + } + + if(!propfind || !propfind->properties) { + return 1; + } + + *out_sn = sn; + *out_rq = rq; + *out_propfind = propfind; + return 0; +} + +static WebdavOperation* test_propfind_op( + Session **out_sn, + Request **out_rq, + const char *xml) +{ + WebdavPropfindRequest *propfind; + if(test_init(out_sn, out_rq, &propfind, xml)) { + return NULL; + } + + Multistatus *ms = multistatus_response(*out_sn, *out_rq); + if(!ms) { + return NULL; + } + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + UcxList *requests = NULL; + + // Initialize all Webdav Backends + if(webdav_propfind_init(&backend1, propfind, "/", "/", &requests)) { + return NULL; + } + + return webdav_create_propfind_operation( + (*out_sn), + (*out_rq), + &backend1, + propfind->properties, + requests, + response); +} + + +UCX_TEST(test_webdav_plist_add) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + WebdavPList *begin = NULL; + WebdavPList *end = NULL; + + WebdavProperty p1, p2, p3; + ZERO(&p1, sizeof(WebdavProperty)); + ZERO(&p2, sizeof(WebdavProperty)); + ZERO(&p3, sizeof(WebdavProperty)); + int r; + + r = webdav_plist_add(sn->pool, &begin, &end, &p1); + + UCX_TEST_ASSERT(r == 0, "add 1 failed"); + UCX_TEST_ASSERT(begin && end, "ptrs are NULL"); + UCX_TEST_ASSERT(begin == end, "begin != end"); + UCX_TEST_ASSERT(begin->prev == NULL, "begin->prev not NULL"); + UCX_TEST_ASSERT(begin->next == NULL, "begin->next not NULL"); + + r = webdav_plist_add(sn->pool, &begin, &end, &p2); + + UCX_TEST_ASSERT(r == 0, "add 2 failed"); + UCX_TEST_ASSERT(begin && end, "add2: ptrs are NULL"); + UCX_TEST_ASSERT(begin->next, "begin->next is NULL"); + UCX_TEST_ASSERT(begin->next == end, "begin->next != end"); + UCX_TEST_ASSERT(end->prev = begin, "end->prev != begin"); + UCX_TEST_ASSERT(begin->prev == NULL, "add2: begin->prev not NULL"); + UCX_TEST_ASSERT(end->next == NULL, "add2: end->next not NULL"); + + r = webdav_plist_add(sn->pool, &begin, &end, &p3); + + UCX_TEST_ASSERT(r == 0, "add 3 failed"); + UCX_TEST_ASSERT(begin && end, "add3: ptrs are NULL"); + UCX_TEST_ASSERT(begin->next == end->prev, "begin->next != end->prev"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_plist_size) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + WebdavPList *begin = NULL; + WebdavPList *end = NULL; + + WebdavProperty p1, p2, p3; + ZERO(&p1, sizeof(WebdavProperty)); + ZERO(&p2, sizeof(WebdavProperty)); + ZERO(&p3, sizeof(WebdavProperty)); + int r; + + UCX_TEST_ASSERT(webdav_plist_size(begin) == 0, "size != 0"); + r = webdav_plist_add(sn->pool, &begin, &end, &p1); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 1, "size != 1"); + r = webdav_plist_add(sn->pool, &begin, &end, &p2); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 2, "size != 2"); + r = webdav_plist_add(sn->pool, &begin, &end, &p3); + UCX_TEST_ASSERT(webdav_plist_size(begin) == 3, "size != 3"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_propfind_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPFIND", "/"); + + UCX_TEST_BEGIN + + int error = 0; + + // + // ----------------- TEST_PROPFIND1 ----------------- + // test basic propfind request + WebdavPropfindRequest *p1 = propfind_parse( + sn, + rq, + TEST_PROPFIND1, + strlen(TEST_PROPFIND1), + &error); + + UCX_TEST_ASSERT(p1, "p1 is NULL"); + UCX_TEST_ASSERT(p1->properties, "p1: no props"); + UCX_TEST_ASSERT(!p1->allprop, "p1: allprop is TRUE"); + UCX_TEST_ASSERT(!p1->propname, "p1: propname is TRUE"); + UCX_TEST_ASSERT(p1->propcount == 6, "p1: wrong propcount"); + + // property 1: DAV:displayname + WebdavPList *elm = p1->properties; + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "displayname"), + "p1: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 1 has wrong namespace"); + + // property 2: DAV:getcontentlength + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 2 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "getcontentlength"), + "p1: property 2 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 2 has wrong namespace"); + + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 3 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 4 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 5 missing"); + + // property 6: DAV:getetag + elm = elm->next; + UCX_TEST_ASSERT(elm, "p1: property 6 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "getetag"), + "p1: property 6 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p1: property 6 has wrong namespace"); + UCX_TEST_ASSERT(!elm->next, "p1: should not have property 7"); + + // + // ----------------- TEST_PROPFIND2 ----------------- + // test with multiple namespaces + WebdavPropfindRequest *p2 = propfind_parse( + sn, + rq, + TEST_PROPFIND2, + strlen(TEST_PROPFIND2), + &error); + + UCX_TEST_ASSERT(p2, "p2 is NULL"); + UCX_TEST_ASSERT(p2->properties, "p2: no props"); + UCX_TEST_ASSERT(!p2->allprop, "p2: allprop is TRUE"); + UCX_TEST_ASSERT(!p2->propname, "p2: propname is TRUE"); + + // property 1: DAV:resourcetype + elm = p2->properties; + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "resourcetype"), + "p2: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p2: property 1 has wrong namespace"); + + // property 2: X:testprop + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 2 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "testprop"), + "p2: property 2 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "http://example.com/"), + "p2: property 2 has wrong namespace"); + + // property 3: X:name + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 3 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "name"), + "p2: property 3 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "http://example.com/"), + "p2: property 3 has wrong namespace"); + + // property 4: Z:testprop + elm = elm->next; + UCX_TEST_ASSERT(elm, "p2: property 4 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "testprop"), + "p2: property 4 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "testns"), + "p2: property 4 has wrong namespace"); + + + // + // ----------------- TEST_PROPFIND3 ----------------- + // test allprop + WebdavPropfindRequest *p3 = propfind_parse(sn, rq, TEST_PROPFIND3, strlen(TEST_PROPFIND3), &error); + + UCX_TEST_ASSERT(p3, "p3 is NULL"); + UCX_TEST_ASSERT(!p3->properties, "p2: has props"); + UCX_TEST_ASSERT(p3->allprop, "p2: allprop is FALSE"); + UCX_TEST_ASSERT(!p3->propname, "p2: propname is TRUE"); + UCX_TEST_ASSERT(p3->propcount == 0, "p2: wrong propcount"); + + + // + // ----------------- TEST_PROPFIND4 ----------------- + // test propname + WebdavPropfindRequest *p4 = propfind_parse(sn, rq, TEST_PROPFIND4, strlen(TEST_PROPFIND4), &error); + + UCX_TEST_ASSERT(p4, "p4 is NULL"); + UCX_TEST_ASSERT(!p4->properties, "p2: has props"); + UCX_TEST_ASSERT(!p4->allprop, "p2: allprop is TRUE"); + UCX_TEST_ASSERT(p4->propname, "p2: propname is FALSE"); + + + // + // ----------------- TEST_PROPFIND5 ----------------- + // test duplicate check + WebdavPropfindRequest *p5 = propfind_parse(sn, rq, TEST_PROPFIND5, strlen(TEST_PROPFIND5), &error); + + UCX_TEST_ASSERT(p5, "p5 is NULL"); + UCX_TEST_ASSERT(p5->properties, "p5: no props"); + UCX_TEST_ASSERT(!p5->allprop, "p5: allprop is TRUE"); + UCX_TEST_ASSERT(!p5->propname, "p5: propname is TRUE"); + UCX_TEST_ASSERT(p5->propcount == 4, "p5: wrong propcount"); + + // property 1: DAV:displayname + elm = p5->properties; + UCX_TEST_ASSERT(elm, "p5: property 1 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "displayname"), + "p5: property 1 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p5: property 1 has wrong namespace"); + + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 2 missing"); + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 3 missing"); + + // property 4: DAV:resourcetype + elm = elm->next; + UCX_TEST_ASSERT(elm, "p5: property 4 missing"); + UCX_TEST_ASSERT( + !strcmp(elm->property->name, "resourcetype"), + "p5: property 4 has wrong name"); + UCX_TEST_ASSERT( + !strcmp((char*)elm->property->namespace->href, "DAV:"), + "p5: property 4 has wrong namespace"); + + + // + // ----------------- TEST_PROPFIND6 ----------------- + // test prop/allprop mix + WebdavPropfindRequest *p6 = propfind_parse(sn, rq, TEST_PROPFIND6, strlen(TEST_PROPFIND6), &error); + + UCX_TEST_ASSERT(p6, "p5 is NULL"); + UCX_TEST_ASSERT(!p6->properties, "p5: has props"); + UCX_TEST_ASSERT(p6->allprop, "p5: allprop is FALSE"); + UCX_TEST_ASSERT(!p6->propname, "p5: propname is TRUE"); + UCX_TEST_ASSERT(p6->propcount == 0, "p5: wrong propcount"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_proppatch_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPPATCH", "/"); + + UCX_TEST_BEGIN + int error = 0; + + WebdavProppatchRequest *p1 = proppatch_parse(sn, rq, TEST_PROPPATCH1, strlen(TEST_PROPPATCH1), &error); + + UCX_TEST_ASSERT(p1->set, "p1: missing set props"); + UCX_TEST_ASSERT(!p1->remove, "p1: has remove props"); + UCX_TEST_ASSERT(p1->setcount == 2, "p1: wrong setcount"); + UCX_TEST_ASSERT(p1->set->next, "p1: set plist broken"); + UCX_TEST_ASSERT(!p1->set->next->next, "p1: set plist has no end"); + UCX_TEST_ASSERT(p1->set->property, "p1: missing property ptr in plist"); + UCX_TEST_ASSERT( + !strcmp(p1->set->property->name, "test"), + "p1: wrong property 1 name"); + + WebdavProppatchRequest *p2 = proppatch_parse(sn, rq, TEST_PROPPATCH2, strlen(TEST_PROPPATCH2), &error); + + UCX_TEST_ASSERT(p2->set, "p2: missing set props"); + UCX_TEST_ASSERT(p2->remove, "p2: missing remove props"); + UCX_TEST_ASSERT(p2->setcount == 4, "p2: wrong setcount"); + UCX_TEST_ASSERT(p2->removecount == 1, "p2: wrong removecount"); + + UCX_TEST_ASSERT( + !strcmp((char*)p2->set->property->namespace->href, "http://example.com/"), + "p2: set property 1: wrong namespace"); + UCX_TEST_ASSERT( + !strcmp(p2->set->property->name, "a"), + "p2: set property 1: wrong name"); + WSXmlNode *p2set1 = p2->set->property->value.node; + UCX_TEST_ASSERT( + p2set1->type == WS_NODE_TEXT, + "p2: set property 1: wrong type"); + UCX_TEST_ASSERT( + p2set1->content, + "p2: set property 1: no text"); + UCX_TEST_ASSERT( + !strcmp((char*)p2set1->content, "test"), + "p2: set property 1: wrong value"); + + WSXmlNode *p2set3 = p2->set->next->next->property->value.node; + UCX_TEST_ASSERT(p2set3, "p2: set property 3 missing"); + UCX_TEST_ASSERT( + p2set3->type == WS_NODE_TEXT, + "p2: set property 3: wrong type"); + UCX_TEST_ASSERT( + p2set3->next, + "p2: set property 3: missing element X:name"); + + UCX_TEST_ASSERT( + xmlHasProp(p2set3->next, BAD_CAST"test"), + "p2: set property 3: missing attribute 'test'"); + + UCX_TEST_ASSERT( + xmlHasProp(p2set3->next, BAD_CAST"abc"), + "p2: set property 3: missing attribute 'abc"); + + xmlChar *value1 = xmlGetProp(p2set3->next, BAD_CAST"test"); + UCX_TEST_ASSERT( + !strcmp((char*) value1, "test1"), + "p2: set property 3: wrong attribute value 1"); + xmlFree(value1); + + xmlChar *value2 = xmlGetProp(p2set3->next, BAD_CAST"abc"); + UCX_TEST_ASSERT( + !strcmp((char*) value2, "def"), + "p2: set property 3: wrong attribute value 2"); + xmlFree(value2); + + UCX_TEST_ASSERT( + !strcmp(p2->remove->property->name, "e"), + "p2: wrong remove property"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_lock_parse) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "LOCK", "/"); + + UCX_TEST_BEGIN + int error = 0; + + WebdavLockRequest *l1 = lock_parse(sn, rq, TEST_LOCK1, strlen(TEST_LOCK1), &error); + + UCX_TEST_ASSERT(l1, "l1 is NULL"); + UCX_TEST_ASSERT(l1->type == WEBDAV_LOCK_WRITE, "l1: wrong type"); + UCX_TEST_ASSERT(l1->scope == WEBDAV_LOCK_SHARED, "l1: wrong scope"); + UCX_TEST_ASSERT(l1->owner, "l1: owner is NULL"); + UCX_TEST_ASSERT(!strcmp((char*)l1->owner->content, "User"), "l1: wrong owner"); + + UCX_TEST_END + + pool_destroy(sn->pool); +} + +UCX_TEST(test_rqbody2buffer) { + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + // + // TEST 1 + sn = testutil_session(); + rq = testutil_request(sn->pool, "PUT", "/"); + testutil_request_body(sn, rq, "Hello World!", 12); + + UcxBuffer *b1 = rqbody2buffer(sn, rq); + UCX_TEST_ASSERT(b1->size == 12, "b1: wrong size"); + UCX_TEST_ASSERT(!memcmp(b1->space,"Hello World!",12), "b1: wrong content"); + + ucx_buffer_free(b1); + testutil_destroy_session(sn); + + // + // TEST 2 + size_t len1 = 25000; + unsigned char *body1 = malloc(len1); + for(int i=0;ipool, "PUT", "/"); + testutil_request_body(sn, rq, (char*)body1, len1); + + UcxBuffer *b2 = rqbody2buffer(sn, rq); + UCX_TEST_ASSERT(b2->size == len1, "b2: wrong size"); + UCX_TEST_ASSERT(!memcmp(b2->space, body1, len1), "b2: wrong content"); + + ucx_buffer_free(b2); + testutil_destroy_session(sn); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_plist_iterator) { + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + WebdavPList *properties = propfind->properties; + size_t count = 0; + + WebdavPListIterator i = webdav_plist_iterator(&properties); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + switch(i.index) { + case 0: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "displayname"), "wrong property 1"); + break; + } + case 1: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontentlength"), "wrong property 2"); + break; + } + case 2: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getcontenttype"), "wrong property 3"); + break; + } + case 3: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getlastmodified"), "wrong property 4"); + break; + } + case 4: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "resourcetype"), "wrong property 5"); + break; + } + case 5: { + UCX_TEST_ASSERT(!strcmp(cur->property->name, "getetag"), "wrong property 6"); + break; + } + } + count++; + } + + UCX_TEST_ASSERT(count == propfind->propcount, "wrong count"); + + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_plist_iterator_remove_current) { + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + WebdavPList *properties1 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties2 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties3 = webdav_plist_clone(sn->pool, propfind->properties); + WebdavPList *properties4 = webdav_plist_clone(sn->pool, propfind->properties); + + WebdavPListIterator i; + WebdavPList *cur; + + // test removal of first element + i = webdav_plist_iterator(&properties1); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 0) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(!properties1->prev, "test1: prev not cleared"); + UCX_TEST_ASSERT(!strcmp(properties1->property->name, "getcontentlength"), "test1: wrong property"); + UCX_TEST_ASSERT(!strcmp(properties1->next->property->name, "getcontenttype"), "test1: wrong property 2"); + UCX_TEST_ASSERT(properties1->next->prev == properties1, "test1: wrong link"); + + // test removal of second element + i = webdav_plist_iterator(&properties2); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 1) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(!strcmp(properties2->next->property->name, "getcontenttype"), "test2: wrong property"); + UCX_TEST_ASSERT(properties2->next->prev == properties2, "test2: wrong link"); + UCX_TEST_ASSERT(webdav_plist_size(properties2) == 5, "test2: wrong size"); + + // remove last element + i = webdav_plist_iterator(&properties3); + while(webdav_plist_iterator_next(&i, &cur)) { + if(i.index == 5) { + webdav_plist_iterator_remove_current(&i); + } + } + + UCX_TEST_ASSERT(webdav_plist_size(properties3) == 5, "test3: wrong size"); + UCX_TEST_ASSERT(!strcmp(properties3->next->next->next->next->property->name, "resourcetype"), "test2: wrong property"); + + // remove all elements + i = webdav_plist_iterator(&properties4); + while(webdav_plist_iterator_next(&i, &cur)) { + webdav_plist_iterator_remove_current(&i); + switch(i.index) { + case 0: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontentlength"), "test4: wrong property 2"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (0)"); + break; + } + case 1: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getcontenttype"), "test4: wrong property 3"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (1)"); + break; + } + case 2: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getlastmodified"), "test4: wrong property 4"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (2)"); + break; + } + case 3: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "resourcetype"), "test4: wrong property 5"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (3)"); + break; + } + case 4: { + UCX_TEST_ASSERT(!strcmp(properties4->property->name, "getetag"), "test4: wrong property 6"); + UCX_TEST_ASSERT(properties4->prev == NULL, "test4: prev not NULL (4)"); + break; + } + default: { + UCX_TEST_ASSERT(i.index <= 5, "fail"); + } + } + } + + UCX_TEST_ASSERT(properties4 == NULL, "test4: list not NULL"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_msresponse_addproperty) { + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "init failed"); + UCX_TEST_ASSERT(op->response, "no response"); + + Multistatus *ms = (Multistatus*)op->response; + MSResponse *r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/"); + + WebdavProperty p1; + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + + WSNamespace ns1; + ZERO(&ns1, sizeof(WSNamespace)); + WSNamespace ns2; + ZERO(&ns2, sizeof(WSNamespace)); + ns1.prefix = (xmlChar*)"x1"; + ns1.href = (xmlChar*)"http://example.com/test/"; + ns2.prefix = (xmlChar*)"x2"; + ns2.href = (xmlChar*)"http://example.com/test/"; + + WebdavProperty dp1; + ZERO(&dp1, sizeof(WebdavProperty)); + dp1.name = "dup"; + dp1.namespace = &ns1; + dp1.value.text.str = "Hello"; + dp1.value.text.length = 5; + dp1.vtype = WS_VALUE_TEXT; + + WebdavProperty dp2; + ZERO(&dp2, sizeof(WebdavProperty)); + dp2.name = "dup"; + dp2.namespace = &ns1; + dp2.value.text.str = "Hello"; + dp2.value.text.length = 5; + dp2.vtype = WS_VALUE_TEXT; + + WebdavProperty dp3; + ZERO(&dp3, sizeof(WebdavProperty)); + dp3.name = "dup"; + dp3.namespace = &ns2; + dp3.value.text.str = "Hello"; + dp3.value.text.length = 5; + dp3.vtype = WS_VALUE_TEXT; + + // init test data + p1.namespace = webdav_dav_namespace(); + p1.lang = NULL; + p1.name = "test1"; + p1.value.data = (WSXmlData){ NULL, NULL, 0}; + p1.vtype = 0; + + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_ASSERT(!r->plist_begin && !r->plist_end, "plist not empty"); + + r->resource.addproperty((WebdavResource*)r, &p1, 200); + UCX_TEST_ASSERT(r->plist_begin, "!plist_begin"); + UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end"); + + r->resource.addproperty((WebdavResource*)r, &p[0], 404); + r->resource.addproperty((WebdavResource*)r, &p[1], 404); + r->resource.addproperty((WebdavResource*)r, &p[2], 403); + r->resource.addproperty((WebdavResource*)r, &p[3], 403); + r->resource.addproperty((WebdavResource*)r, &p[4], 403); + r->resource.addproperty((WebdavResource*)r, &p[5], 403); + r->resource.addproperty((WebdavResource*)r, &p[6], 500); + + UCX_TEST_ASSERT(r->plist_begin == r->plist_end, "plist begin != end"); + + UCX_TEST_ASSERT(r->errors, "no prop errors"); + UCX_TEST_ASSERT(r->errors->next, "no second error code"); + UCX_TEST_ASSERT(r->errors->next->next, "no third error code"); + UCX_TEST_ASSERT(!r->errors->next->next->next, "too many error codes"); + + UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 2, "404 list size != 2"); + UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->begin) == 4, "403 list size != 4"); + UCX_TEST_ASSERT(webdav_plist_size(r->errors->next->next->begin) == 1, "500 list size != 1"); + + // new resource for prop duplication tests + r = (MSResponse*)ms->response.addresource((WebdavResponse*)ms, "/test"); + UCX_TEST_ASSERT(r, "cannot create second response"); + + r->resource.addproperty((WebdavResource*)r, &dp1, 200); + UCX_TEST_ASSERT(r->plist_begin, "adding dp1 failed"); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: list size not 1"); + + r->resource.addproperty((WebdavResource*)r, &dp2, 200); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 should not work"); + + r->resource.addproperty((WebdavResource*)r, &dp2, 404); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp2 with different status should not work (1)"); + if(r->errors) { + UCX_TEST_ASSERT(webdav_plist_size(r->errors->begin) == 0, "dp1: error list not empty"); + } + + r->resource.addproperty((WebdavResource*)r, &dp3, 200); + UCX_TEST_ASSERT(!r->plist_begin->next, "dp1: adding dp3 should not work"); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_propfind_init) { + reset_backends(); + + Session *sn; + Request *rq; + WebdavPropfindRequest *propfind; + UCX_TEST_BEGIN; + UCX_TEST_ASSERT(!test_init(&sn, &rq, &propfind, TEST_PROPFIND1), "init failed"); + + UcxList *requests = NULL; + int err = webdav_propfind_init(&backend1, propfind, "/", "/", &requests); + + UCX_TEST_ASSERT(!err, "webdav_propfind_init failed"); + UCX_TEST_ASSERT(requests, "request list is empty"); + UCX_TEST_ASSERT(ucx_list_size(requests), "request list has wrong size"); + + WebdavPropfindRequest *p1 = requests->data; + WebdavPropfindRequest *p2 = requests->next->data; + + // backend1 removes the first property from the plist + // backend2 should have one property less + + UCX_TEST_ASSERT(p1 && p2, "missing requests objects"); + UCX_TEST_ASSERT(p1 != p2, "request objects equal"); + UCX_TEST_ASSERT(p1->properties != p2->properties, "plists equal"); + UCX_TEST_ASSERT(p1->propcount == p2->propcount + 1, "first property not removed"); + + UCX_TEST_ASSERT(backend1_init_called == 1, "backend1 init not called"); + UCX_TEST_ASSERT(backend2_init_called == 1, "backend2 init not called"); + + UCX_TEST_END; + + pool_destroy(sn->pool); +} + +UCX_TEST(test_webdav_op_propfind_begin) { + reset_backends(); + + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "WebdavOperation not created"); + + int err = webdav_op_propfind_begin(op, "/", NULL, NULL); + UCX_TEST_ASSERT(err == 0, "err not 0"); + UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend2 propfind_do not called"); + + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_op_propfind_children) { + reset_backends(); + + Session *sn; + Request *rq; + + UCX_TEST_BEGIN; + WebdavOperation *op = test_propfind_op(&sn, &rq, TEST_PROPFIND1); + UCX_TEST_ASSERT(op, "WebdavOperation not created"); + + int err = webdav_op_propfind_begin(op, "/", NULL, NULL); + UCX_TEST_ASSERT(err == 0, "propfind_begin error"); + + // create test vfs with some files (code from test_vfs_readdir) + rq->vfs = testvfs_create(sn); + VFSContext *vfs = vfs_request_context(sn, rq); + UCX_TEST_ASSERT(vfs, "no vfs"); + + err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + // add some test file to /dir + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "dir not opened"); + + UCX_TEST_ASSERT(backend1_propfind_do_count == 1, "backend1 propfind_do not called"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 1, "backend1 propfind_do not called") + + // propfind for all children + err = webdav_op_propfind_children(op, vfs, "/", "/dir"); + UCX_TEST_ASSERT(err == 0, "webdav_op_propfind_children failed"); + + // 1 dir + 4 children + UCX_TEST_ASSERT(backend1_propfind_do_count == 5, "backend1 propfind_do wrong count"); + UCX_TEST_ASSERT(backend2_propfind_do_count == 5, "backend2 propfind_do wrong count"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +void init_test_webdav_method( + Session **out_sn, + Request **out_rq, + TestIOStream **out_st, + pblock **out_pb, + const char *method, + const char *path, + const char *request_body) +{ + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + sn = testutil_session(); + rq = testutil_request(sn->pool, method, "/"); + + pblock_nvinsert("path", path, rq->vars); + pblock_nvinsert("uri", path, rq->reqpb); + + st = testutil_iostream(2048, TRUE); + sn->csd = (IOStream*)st; + + if(request_body) { + testutil_request_body(sn, rq, request_body, strlen(request_body)); + } + + pb = pblock_create_pool(sn->pool, 4); + + *out_sn = sn; + *out_rq = rq; + *out_st = st; + *out_pb = pb; +} + +UCX_TEST(test_webdav_propfind) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + int ret; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND1); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed"); + + xmlDoc *doc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "propfind1: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + testutil_destroy_session(sn); + xmlFreeDoc(doc); + testutil_iostream_destroy(st); + + // Test2 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/", TEST_PROPFIND2); + + ret = webdav_propfind(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed"); + + xmlDoc *doc2 = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "propfind2: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + testutil_destroy_session(sn); + xmlFreeDoc(doc2); + testutil_iostream_destroy(st); + + UCX_TEST_END; + +} + +/* ------------------------------------------------------------------------- + * + * PROPPATCH TESTS + * + * ------------------------------------------------------------------------ */ + +static int test_proppatch_init( + Session **out_sn, + Request **out_rq, + WebdavProppatchRequest **out_proppatch, + const char *xml) +{ + if(!webdav_is_initialized) { + if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) { + return 1; + } + webdav_is_initialized = 1; + } + + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPPATCH", "/"); + + int error = 0; + + WebdavProppatchRequest *proppatch = proppatch_parse( + sn, + rq, + xml, + strlen(xml), + &error); + + if(error) { + return 1; + } + + if(!proppatch || !(proppatch->set || proppatch->remove)) { + return 1; + } + + *out_sn = sn; + *out_rq = rq; + *out_proppatch = proppatch; + return 0; +} + +static WebdavOperation* test_proppatch_op1( + Session **out_sn, + Request **out_rq, + const char *xml) +{ + WebdavProppatchRequest *proppatch; + if(test_proppatch_init(out_sn, out_rq, &proppatch, xml)) { + return NULL; + } + + Multistatus *ms = multistatus_response(*out_sn, *out_rq); + if(!ms) { + return NULL; + } + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + return webdav_create_proppatch_operation( + (*out_sn), + (*out_rq), + &backend1, + proppatch, + response); +} + + +UCX_TEST(test_proppatch_msresponse) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[i].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 200), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + MSResponse *msres = (MSResponse*)res; + UCX_TEST_ASSERT(!msres->errors, "error list not NULL"); + UCX_TEST_ASSERT(msres->plist_begin, "elm1 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next, "elm2 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next, "elm3 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next->next, "elm4 missing"); + UCX_TEST_ASSERT(!msres->plist_begin->next->next->next->next, "count != 4"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_msresponse_addproperty_with_errors) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[i].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 409), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + // all properties should have an error status code now + // 1 x 409, 3 x 424 + + MSResponse *msres = (MSResponse*)res; + + UCX_TEST_ASSERT(!msres->plist_begin, "plist not NULL"); + UCX_TEST_ASSERT(msres->errors, "error list is NULL"); + UCX_TEST_ASSERT(msres->errors->next, "second error list is missing"); + UCX_TEST_ASSERT(!msres->errors->next->next, "wrong error list size"); + + // We know that we have 2 error lists, one with status code 409 and + // the other must have 409. However we don't enforce the order of the + // error lists, therefore check both variants + if(msres->errors->status == 409) { + UCX_TEST_ASSERT(msres->errors->next->status == 424, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 409 property"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 424 properties"); + } else { + UCX_TEST_ASSERT(msres->errors->next->status == 409, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 424 properties"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 409 property"); + } + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_webdav_op_proppatch) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + // TEST_PROPPATCH2 should succeed + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + int ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed"); + UCX_TEST_ASSERT(backend1_proppatch_commit, "backend1 no commit"); + UCX_TEST_ASSERT(backend2_proppatch_commit, "backend2 no commit"); + UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (1)"); + UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (1)"); + UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (1)"); + UCX_TEST_ASSERT(backend2_proppatch_finish_count == 1, "backend1 wrong finish count (1)"); + + // TEST_PROPPATCH3 should fail (commit == FALSE) + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH3); + UCX_TEST_ASSERT(op, "failed to create proppatch operation 2"); + + ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret == 0, "webdav_op_proppatch failed (2)"); + UCX_TEST_ASSERT(!backend1_proppatch_commit, "backend1 commit"); + UCX_TEST_ASSERT(!backend2_proppatch_commit, "backend2 commit"); + + // TEST_PROPPATCH4 should abort + reset_backends(); + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH4); + UCX_TEST_ASSERT(op, "failed to create proppatch operation 3"); + + ret = webdav_op_proppatch(op, "/", "/"); + UCX_TEST_ASSERT(ret != 0, "webdav_op_proppatch should fail"); + UCX_TEST_ASSERT(backend1_proppatch_do_count == 1, "backend1 wrong count (2)"); + UCX_TEST_ASSERT(backend2_proppatch_do_count == 1, "backend1 wrong count (2)"); + UCX_TEST_ASSERT(backend1_proppatch_finish_count == 1, "backend1 wrong finish count (2)"); + UCX_TEST_ASSERT(backend2_proppatch_finish_count == 0, "backend1 wrong finish count (2)"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +#define xstreq(a, b) (!strcmp((const char*)a, (const char*)b)) + +UCX_TEST(test_webdav_proppatch) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + UCX_TEST_BEGIN; + + int ret; + // Test 1 + init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/", TEST_PROPPATCH2); + rq->davCollection = &backend1; + ret = webdav_proppatch(pb, sn, rq); + + UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_proppatch (1) failed"); + + xmlDoc *doc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "proppatch1: response is not valid xml"); + + //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space); + + xmlNode *root = xmlDocGetRootElement(doc); + UCX_TEST_ASSERT(root, "proppatch1: no root"); + + xmlNode *nodeC = NULL; + xmlNode *node = root->children; + int depth = 1; + while(node) { + const xmlChar *name = node->name; + int nextNode = 1; + if(node->type != XML_ELEMENT_NODE) { + // nothing + } else if(depth == 1) { + if(xstreq(name, "response")) { + nextNode = 0; + } + } else if(depth == 2) { + if(xstreq(name, "propstat")) { + nextNode = 0; + } + } else if(depth == 3) { + if(xstreq(name, "prop")) { + nextNode = 0; + } + } else if(depth == 4) { + if(xstreq(name, "c")) { + nodeC = node; + break; + } + } + + if(nextNode) { + node = node->next; + } else { + node = node->children; + depth++; + } + } + + UCX_TEST_ASSERT(nodeC, "prop c not in response"); + UCX_TEST_ASSERT(!nodeC->children, "properties must not have a value"); + + testutil_destroy_session(sn); + xmlFreeDoc(doc); + testutil_iostream_destroy(st); + + + UCX_TEST_END; +} + + +/* ------------------------------------------------------------------------- + * + * WEBDAV VFS TESTS + * + * ------------------------------------------------------------------------ */ + +static int mkcol_data1 = 10; +static int mkcol_data2 = 20; +static int mkcol_data3 = 30; +static int mkcol_data4 = 40; + +static int mkcol_count = 0; +static int mkcol_finish_count = 0; + +static int mkcol_err = 0; + +static int set_created = 0; + +static int test_webdav_mkcol(WebdavVFSRequest *req, WSBool *created) { + mkcol_count++; + + switch(mkcol_count) { + case 1: { + req->userdata = &mkcol_data1; + break; + } + case 2: { + req->userdata = &mkcol_data2; + break; + } + case 3: { + req->userdata = &mkcol_data3; + break; + } + case 4: { + req->userdata = &mkcol_data4; + break; + } + default: break; + } + + if(set_created) { + *created = TRUE; + set_created = 0; + } + + return 0; +} + +static int test_webdav_mkcol_finish(WebdavVFSRequest *req, WSBool success) { + mkcol_finish_count++; + + if(mkcol_finish_count == 1) { + int *data = req->userdata; + if(data != &mkcol_data1) { + mkcol_err = 1; + } + } else if(mkcol_finish_count == 3) { + int *data = req->userdata; + if(data != &mkcol_data3) { + mkcol_err = 1; + } + } else { + int *data = req->userdata; + // data4 should never be used + if(data == &mkcol_data4) { + mkcol_err = 1; + } + } + + return 0; +} + +static int test_webdav_mkcol_fail(WebdavVFSRequest *req, WSBool *created) { + mkcol_count++; + return 1; +} + +static int delete_count = 0; +static int delete_finish_count = 0; + +static int test_backend_webdav_delete(WebdavVFSRequest *req, WSBool *created) { + delete_count++; + return 0; +} + +static int test_backend_webdav_delete_finish(WebdavVFSRequest *req, WSBool success) { + delete_finish_count++; + return 0; +} + + +UCX_TEST(test_webdav_vfs_op_do) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + // Tests performed primarily with MKCOL, because webdav_vfs_op_do + // behaves the same for both operations + // the only difference are the callbacks + + init_test_webdav_method(&sn, &rq, &st, &pb, "MKCOL", "/", NULL); + VFS *testvfs = testvfs_create(sn); + rq->vfs = testvfs; + + WebdavBackend dav1; + ZERO(&dav1, sizeof(WebdavBackend)); + dav1.opt_mkcol = test_webdav_mkcol; + dav1.opt_mkcol_finish = test_webdav_mkcol_finish; + dav1.opt_delete = test_backend_webdav_delete; + dav1.opt_delete_finish = test_backend_webdav_delete_finish; + + WebdavBackend dav2; + ZERO(&dav2, sizeof(WebdavBackend)); + dav2.opt_mkcol_finish = test_webdav_mkcol_finish; + + WebdavBackend dav3; + ZERO(&dav3, sizeof(WebdavBackend)); + dav3.opt_mkcol = test_webdav_mkcol; + + WebdavBackend dav4; + ZERO(&dav4, sizeof(WebdavBackend)); + dav4.opt_mkcol = test_webdav_mkcol; + dav4.opt_mkcol_finish = test_webdav_mkcol_finish; + + dav1.next = &dav2; + dav2.next = &dav3; + dav3.next = &dav4; + + rq->davCollection = &dav1; + + UCX_TEST_BEGIN; + + WebdavVFSOperation *op1 = webdav_vfs_op(sn, rq, &dav1, FALSE); + + int ret = webdav_vfs_op_do(op1, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(!ret, "webdav_vfs_op_do failed"); + UCX_TEST_ASSERT(mkcol_count == 3, "wrong mkcol_count"); + UCX_TEST_ASSERT(mkcol_finish_count == 3, "wrong mkcol_finish_count"); + UCX_TEST_ASSERT(mkcol_err == 0, "mkcol_err"); + + // test without VFS, but set *created to TRUE to skip VFS usage + rq->vfs = NULL; + set_created = 1; + + WebdavVFSOperation *op2 = webdav_vfs_op(sn, rq, &dav1, FALSE); + ret = webdav_vfs_op_do(op2, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(!ret, "op2 failed"); + + // test 3: abort after first backend + mkcol_count = 0; + mkcol_finish_count = 0; + dav1.opt_mkcol = test_webdav_mkcol_fail; + + WebdavVFSOperation *op3 = webdav_vfs_op(sn, rq, &dav1, FALSE); + ret = webdav_vfs_op_do(op3, WEBDAV_VFS_MKDIR); + + UCX_TEST_ASSERT(ret, "op3 should fail"); + UCX_TEST_ASSERT(mkcol_count == 1, "op3: wrong mkcol_count"); + UCX_TEST_ASSERT(mkcol_finish_count == 1, "op3: wrong mkcol_finish_count"); + + // test DELETE to make sure, delete callbacks will be used + pblock_replace("path", "/deltest", rq->vars); + rq->vfs = testvfs; + WebdavVFSOperation *op_del = webdav_vfs_op(sn, rq, &dav1, FALSE); + vfs_open(op_del->vfs, "/deltest", O_CREAT); + ret = webdav_vfs_op_do(op_del, WEBDAV_VFS_DELETE); + + UCX_TEST_ASSERT(!ret, "op_del failed"); + UCX_TEST_ASSERT(delete_count == 1, "op_del: wrong delete_count"); + UCX_TEST_ASSERT(delete_finish_count == 1, "op_del: wrong delete_finish_count"); + + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_delete){ + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + init_test_webdav_method(&sn, &rq, &st, &pb, "DELETE", "/", NULL); + rq->vfs = testvfs_create(sn); + + WebdavBackend dav1; + ZERO(&dav1, sizeof(WebdavBackend)); + dav1.opt_delete = test_backend_webdav_delete; + dav1.opt_delete_finish = test_backend_webdav_delete_finish; + delete_count = 0; + delete_finish_count = 0; + rq->davCollection = &dav1; + + UCX_TEST_BEGIN; + + // prepare + VFSContext *vfs = vfs_request_context(sn, rq); + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir dir1 failed"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir dir2 failed"); + err = vfs_mkdir(vfs, "/dir2/dir3"); + UCX_TEST_ASSERT(err == 0, "mkdir dir3 failed"); + err = vfs_mkdir(vfs, "/dir2/dir4"); + UCX_TEST_ASSERT(err == 0, "mkdir dir4 failed"); + err = vfs_mkdir(vfs, "/dir2/dir4/dir5"); + UCX_TEST_ASSERT(err == 0, "mkdir dir5 failed"); + + SYS_FILE f0 = vfs_open(vfs, "/file0", O_CREAT); + UCX_TEST_ASSERT(f0, "f0 create failed"); + // no f1 + SYS_FILE f2 = vfs_open(vfs, "/dir2/file2", O_CREAT); + UCX_TEST_ASSERT(f2, "f2 create failed"); + SYS_FILE f3 = vfs_open(vfs, "/dir2/dir3/file3", O_CREAT); + UCX_TEST_ASSERT(f3, "f3 create failed"); + SYS_FILE f4 = vfs_open(vfs, "/dir2/dir4/file4", O_CREAT); + UCX_TEST_ASSERT(f4, "f4 create failed"); + SYS_FILE f5 = vfs_open(vfs, "/dir2/dir4/dir5/file5", O_CREAT); + UCX_TEST_ASSERT(f5, "f5 create failed"); + + // delete single file + pblock_replace("path", "/file0", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /file0 failed"); + UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count"); + + delete_count = 0; + pblock_replace("path", "/dir1", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /dir1 failed"); + UCX_TEST_ASSERT(delete_count == 1, "del1: wrong delete count"); + + delete_count = 0; + pblock_replace("path", "/dir2", rq->vars); + err = webdav_delete(NULL, sn, rq); + UCX_TEST_ASSERT(err == 0, "DELETE /dir2 failed"); + UCX_TEST_ASSERT(delete_count == 8, "del2: wrong delete count"); + + UCX_TEST_END; +} + +UCX_TEST(test_webdav_put) { + Session *sn; + Request *rq; + TestIOStream *st; + pblock *pb; + + const char *content_const = "Hello World"; + + init_test_webdav_method(&sn, &rq, &st, &pb, "PUT", "/", content_const); + rq->vfs = testvfs_create(sn); + + UCX_TEST_BEGIN; + + int err; + + pblock_replace("path", "/file0", rq->vars); + err = webdav_put(NULL, sn, rq); + + UCX_TEST_ASSERT(err == REQ_PROCEED, "put failed"); + + VFSContext *vfs = vfs_request_context(sn, rq); + SYS_FILE f0 = vfs_open(vfs, "/file0", 0); + UCX_TEST_ASSERT(f0, "cannot open file0"); + + char buf[1024]; + int r = system_fread(f0, buf, 1024); + + UCX_TEST_ASSERT(r == strlen(content_const), "wrong file size"); + UCX_TEST_ASSERT(!memcmp(content_const, buf, r), "wrong file content"); + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/test/webdav.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/webdav.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,203 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_WEBDAV_H +#define TEST_WEBDAV_H + +#include "../public/nsapi.h" +#include "../public/webdav.h" + +#include "testutils.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void init_test_webdav_method( + Session **out_sn, + Request **out_rq, + TestIOStream **out_st, + pblock **out_pb, + const char *method, + const char *path, + const char *request_body); + +UCX_TEST(test_webdav_plist_add); +UCX_TEST(test_webdav_plist_size); + +UCX_TEST(test_propfind_parse); +UCX_TEST(test_proppatch_parse); +UCX_TEST(test_lock_parse); + +UCX_TEST(test_rqbody2buffer); + +UCX_TEST(test_webdav_plist_iterator); +UCX_TEST(test_webdav_plist_iterator_remove_current); + +UCX_TEST(test_msresponse_addproperty); +UCX_TEST(test_msresponse_addproperty_with_errors); + +UCX_TEST(test_webdav_propfind_init); +UCX_TEST(test_webdav_op_propfind_begin); +UCX_TEST(test_webdav_op_propfind_children); + +UCX_TEST(test_webdav_propfind); + +UCX_TEST(test_proppatch_msresponse); +UCX_TEST(test_webdav_op_proppatch); + +UCX_TEST(test_webdav_proppatch); + +UCX_TEST(test_webdav_vfs_op_do); + +UCX_TEST(test_webdav_delete); +UCX_TEST(test_webdav_put); + +/* --------------------------- PROPFIND --------------------------- */ + +#define TEST_PROPFIND1 " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define TEST_PROPFIND2 " \ + \ + \ + \ + \ + \ + \ + \ + " + +#define TEST_PROPFIND3 " \ + \ + \ + " + +#define TEST_PROPFIND4 " \ + \ + \ + " + +#define TEST_PROPFIND5 " \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define TEST_PROPFIND6 " \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +/* --------------------------- PROPPATCH --------------------------- */ + +#define TEST_PROPPATCH1 " \ + \ + \ + test123 \ + \ + " + +#define TEST_PROPPATCH2 " \ + \ + \ + \ + test \ + 15 \ + \ + Useruser@host \ + \ + Test \ + \ + \ + \ + \ + \ + \ + \ + " + +#define TEST_PROPPATCH3 " \ + \ + \ + \ + test \ + 15 \ + \ + \ + \ + \ + \ + \ + \ + " + +#define TEST_PROPPATCH4 " \ + \ + \ + error \ + \ + " + +/* --------------------------- LOCK --------------------------- */ + +#define TEST_LOCK1 " \ + \ + \ + D:write/> \ + User \ + " + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_WEBDAV_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/writer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/writer.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,151 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "../util/writer.h" + +#include + +#include "writer.h" +#include "testutils.h" + +UCX_TEST(test_writer_putc) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 4); + Writer *out = &writer; + + writer_putc(out, 'a'); + UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos"); + + writer_putc(out, 'b'); + UCX_TEST_ASSERT(wbuf[1] == 'b', "2: wrong char at pos 1"); + UCX_TEST_ASSERT(writer.pos == 2, "2: wrong pos"); + + writer_putc(out, 'c'); + writer_putc(out, 'd'); + UCX_TEST_ASSERT(wbuf[2] == 'c', "3: wrong char at pos 2"); + UCX_TEST_ASSERT(wbuf[3] == 'd', "4: wrong char at pos 3"); + + writer_putc(out, 'f'); // should flush the buffer + UCX_TEST_ASSERT(wbuf[0] == 'f', "5: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "5: wrong pos"); + UCX_TEST_ASSERT(buf->space[0] == 'a', "5: wrong char at UcxBuffer pos 0"); + UCX_TEST_ASSERT(buf->space[1] == 'b', "5: wrong char at UcxBuffer pos 1"); + UCX_TEST_ASSERT(buf->pos == 4, "5: wrong UcxBuffer pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} + +UCX_TEST(test_writer_flush) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 4); + Writer *out = &writer; + + writer_putc(out, 'a'); + UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos"); + + writer_flush(out); + UCX_TEST_ASSERT(writer.pos == 0, "wrong pos after flush"); + UCX_TEST_ASSERT(buf->space[0] == 'a', "wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 1, "wrong UcxBuffer pos"); + + writer_putc(out, 'b'); + UCX_TEST_ASSERT(wbuf[0] == 'b', "2: wrong char at pos 0"); + UCX_TEST_ASSERT(writer.pos == 1, "2: wrong pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} + +UCX_TEST(test_writer_put) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + UcxBuffer *buf = st->buf; + + UCX_TEST_BEGIN; + + Writer writer; + char wbuf[1024]; + writer_init(&writer, st, wbuf, 8); + Writer *out = &writer; + + writer_put(out, "abcd", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "abcd", 4), "1: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "1: wrong pos"); + + writer_put(out, "efgh", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "abcdefgh", 8), "2: wrong content"); + UCX_TEST_ASSERT(writer.pos == 8, "2: wrong pos"); + + writer_put(out, "1234", 4); + UCX_TEST_ASSERT(!memcmp(wbuf, "1234", 4), "3: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "3: wrong pos"); + UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh", 8), "3: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 8, "3: wrong UcxBuffer pos"); + + writer_put(out, "5678xx", 6); + UCX_TEST_ASSERT(!memcmp(wbuf, "xx", 2), "4: wrong content"); + UCX_TEST_ASSERT(writer.pos == 2, "4: wrong pos"); + UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh12345678", 16), "4: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 16, "4: wrong UcxBuffer pos"); + + writer_puts(out, S("345678abcdefgh12345678end.")); + UCX_TEST_ASSERT(!memcmp(wbuf, "end.", 4), "5: wrong content"); + UCX_TEST_ASSERT(writer.pos == 4, "5: wrong pos"); + UCX_TEST_ASSERT(!memcmp( + buf->space, + "abcdefgh12345678xx345678abcdefgh12345678", + 40), + "5: wrong UcxBuffer content"); + UCX_TEST_ASSERT(buf->pos == 40, "5: wrong UcxBuffer pos"); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/test/writer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/writer.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_WRITER_H +#define TEST_WRITER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_writer_putc); +UCX_TEST(test_writer_flush); +UCX_TEST(test_writer_put); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_WRITER_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/test/xml.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/xml.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,430 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xml.h" + +#include +#include + +#include + +#include "testutils.h" + +#include "../public/webdav.h" +#include "../util/writer.h" + +#include "../webdav/webdav.h" +#include "../webdav/xml.h" + +typedef struct Test1Data { + int beginCounter; + int endCounter; + int elmCounter; + int endElmCounter; + int textErr; + int err; + int endErr; + int nodesWithAttributesCounter; + xmlNode *prev; +} Test1Data; + +static int test1_begin(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->beginCounter++; + + if(node->type == XML_ELEMENT_NODE) { + data->elmCounter++; + const char *name = (const char*)node->name; + + if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) { + data->err = 1; + return 1; + } + + switch(data->elmCounter) { + case 1: if(strcmp(name, "test")){ data->err = 1; return 1; } break; + case 2: if(strcmp(name, "elm1")) { data->err = 1; return 1; } break; + case 3: if(strcmp(name, "elm2")) { data->err = 1; return 1; } break; + case 4: if(strcmp(name, "c")) { data->err = 1; return 1; } break; + case 5: if(strcmp(name, "a")) { data->err = 1; return 1; } break; + case 6: if(strcmp(name, "d")) { data->err = 1; return 1; } break; + case 7: if(strcmp(name, "e")) { data->err = 1; return 1; } break; + case 8: if(strcmp(name, "b")) { data->err = 1; return 1; } break; + case 9: if(strcmp(name, "x")) { data->err = 1; return 1; } break; + case 10: if(strcmp(name, "z")) { data->err = 1; return 1; } break; + case 11: if(strcmp(name, "nextelm")) { data->err = 1; return 1; } break; + } + } else if(node->type == XML_TEXT_NODE) { + const char *text = (const char*)node->content; + if(!strcmp(text, "teststr")) { + if(strcmp((const char*)data->prev->name, "elm1")) { + data->textErr = 1; + return 1; + } + } else if(!strcmp(text, "hello") || !strcmp(text, "world")) { + if(strcmp((const char*)data->prev->name, "a")) { + data->textErr = 1; + return 1; + } + } + } + + if(node->type == XML_ELEMENT_NODE) { + data->prev = node; + } + return 0; +} + +static int test1_end(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->endCounter++; + + if(node->type == XML_ELEMENT_NODE) { + data->endElmCounter++; + const char *name = (const char*)node->name; + + if(!strcmp(name, "ignore") || !strcmp(name, "ignore")) { + data->err = 1; + return 1; + } + + switch(data->endElmCounter) { + case 1: if(strcmp(name, "elm1")){ data->endErr = 1; return 1; } break; + case 2: if(strcmp(name, "elm2")){ data->endErr = 1; return 1; } break; + case 3: if(strcmp(name, "a")){ data->endErr = 1; return 1; } break; + case 4: if(strcmp(name, "b")){ data->endErr = 1; return 1; } break; + case 5: if(strcmp(name, "e")){ data->endErr = 1; return 1; } break; + case 6: if(strcmp(name, "d")){ data->endErr = 1; return 1; } break; + case 7: if(strcmp(name, "c")){ data->endErr = 1; return 1; } break; + case 8: if(strcmp(name, "z")){ data->endErr = 1; return 1; } break; + case 9: if(strcmp(name, "x")){ data->endErr = 1; return 1; } break; + case 10: if(strcmp(name, "test")){ data->endErr = 1; return 1; } break; + case 11: if(strcmp(name, "nextelm")) { data->endErr = 1; return 1; } break; + } + } + + return 0; +} + +static int test2_begin(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->beginCounter++; + if(node->type == XML_ELEMENT_NODE) { + data->elmCounter++; + if(node->properties) { + data->nodesWithAttributesCounter++; + } + } + return 0; +} + +static int test2_end(xmlNode *node, void *userdata) { + Test1Data *data = userdata; + data->endCounter++; + if(node->type == XML_ELEMENT_NODE) { + data->endElmCounter++; + } + return 0; +} + +UCX_TEST(test_wsxml_iterator) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + xmlDoc *doc = xmlReadMemory( + XML_TESTDATA1, strlen(XML_TESTDATA1), NULL, NULL, 0); + xmlDoc *doc2 = xmlReadMemory( + XML_TESTDATA2, strlen(XML_TESTDATA2), NULL, NULL, 0); + xmlDoc *doc6 = xmlReadMemory( + XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "doc is NULL"); + UCX_TEST_ASSERT(doc2, "doc2 is NULL"); + UCX_TEST_ASSERT(doc6, "doc6 is NULL"); + + xmlNode *root = xmlDocGetRootElement(doc); + + // Test 1: iterate over complete document + Test1Data testdata; + ZERO(&testdata, sizeof(Test1Data)); + int ret = wsxml_iterator(sn->pool, root, test1_begin, test1_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "wsxml_iterator failed"); + UCX_TEST_ASSERT(!testdata.err, "wrong element order (begin)"); + UCX_TEST_ASSERT(!testdata.endErr, "wrong element order (end)"); + UCX_TEST_ASSERT(!testdata.textErr, "text order error"); + UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "begin/end counter not equal"); + + // Test 2: iterate over sub-document + ZERO(&testdata, sizeof(Test1Data)); + xmlNode *root2 = xmlDocGetRootElement(doc2); + xmlNode *sub = root2->children->children; + ret = wsxml_iterator(sn->pool, sub, test1_begin, test1_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "test2: wsxml_iterator failed"); + UCX_TEST_ASSERT(!testdata.err, "test2: wrong element order (begin)"); + UCX_TEST_ASSERT(!testdata.endErr, "test2: wrong element order (end)"); + UCX_TEST_ASSERT(!testdata.textErr, "test2: text order error"); + UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "test2: begin/end counter not equal"); + + // Test 3: iterate over document with all kinds of node types + xmlNode *root6 = xmlDocGetRootElement(doc6); + ZERO(&testdata, sizeof(Test1Data)); + ret = wsxml_iterator(sn->pool, root6, test2_begin, test2_end, &testdata); + UCX_TEST_ASSERT(ret == 0, "test3: wsxml_iterator failed"); + UCX_TEST_ASSERT(testdata.elmCounter == testdata.endElmCounter, "test3: begin/end counter not equal"); + UCX_TEST_ASSERT(testdata.elmCounter == 12, "test3: wrong elm counter"); + UCX_TEST_ASSERT(testdata.nodesWithAttributesCounter == 5, "test3: wrong entity ref counter"); + + xmlFreeDoc(doc); + xmlFreeDoc(doc2); + xmlFreeDoc(doc6); + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +// checks if the namespace list contains the test namespaces x1, x2, x3 and x4 +static void check_ns_list(WebdavNSList *list, int *x1, int *x2, int *x3, int *x4) { + *x1 = 0; + *x2 = 0; + *x3 = 0; + *x4 = 0; + + WebdavNSList *elm = list; + while(elm) { + if(!strcmp((const char*)elm->namespace->prefix, "x1") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns1/")) + { + *x1 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x2") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns2/")) + { + *x2 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x3") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/")) + { + *x3 = 1; + } else if(!strcmp((const char*)elm->namespace->prefix, "x4") && + !strcmp((const char*)elm->namespace->href, "http://example.com/ns_0/")) + { + *x4 = 1; + } + + elm = elm->next; + } +} + +UCX_TEST(test_wsxml_get_required_namespaces) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + xmlDoc *doc3 = xmlReadMemory( + XML_TESTDATA3, strlen(XML_TESTDATA3), NULL, NULL, 0); + xmlDoc *doc4 = xmlReadMemory( + XML_TESTDATA4, strlen(XML_TESTDATA4), NULL, NULL, 0); + xmlDoc *doc5 = xmlReadMemory( + XML_TESTDATA5, strlen(XML_TESTDATA5), NULL, NULL, 0); + + xmlNode *node0 = xmlDocGetRootElement(doc3); + xmlNode *node1 = xmlDocGetRootElement(doc3)->children; + xmlNode *node2 = xmlDocGetRootElement(doc4)->children; + xmlNode *node3 = xmlDocGetRootElement(doc5)->children; + + UCX_TEST_ASSERT(doc3, "doc3 is NULL"); + UCX_TEST_ASSERT(doc4, "doc4 is NULL"); + UCX_TEST_ASSERT(doc5, "doc5 is NULL"); + + int err0, err1, err2, err3; + int x1 = 0; + int x2 = 0; + int x3 = 0; + int x4 = 0; + WebdavNSList *elm = NULL; + + // Test 0: + WebdavNSList *ns0 = wsxml_get_required_namespaces(sn->pool, node0, &err0); + UCX_TEST_ASSERT(!err0, "ns0 failed"); + UCX_TEST_ASSERT(!ns0, "ns0: nsdefs should be ignored"); + + WebdavNSList *ns1 = wsxml_get_required_namespaces(sn->pool, node1, &err1); + check_ns_list(ns1, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err1, "ns1 failed"); + UCX_TEST_ASSERT(ns1, "ns1: no list"); + UCX_TEST_ASSERT(x1, "ns1: x1 missing"); + UCX_TEST_ASSERT(x2, "ns1: x2 missing"); + UCX_TEST_ASSERT(x3, "ns1: x3 missing"); + UCX_TEST_ASSERT(x4, "ns1: x4 missing"); + + WebdavNSList *ns2 = wsxml_get_required_namespaces(sn->pool, node2, &err2); + check_ns_list(ns2, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err2, "ns2 failed"); + UCX_TEST_ASSERT(ns2, "ns2: no list"); + UCX_TEST_ASSERT(x1, "ns2: x1 missing"); + UCX_TEST_ASSERT(x2, "ns2: x2 missing"); + UCX_TEST_ASSERT(!x3, "ns2: x3"); + UCX_TEST_ASSERT(!x4, "ns2: x4"); + + WebdavNSList *ns3 = wsxml_get_required_namespaces(sn->pool, node3, &err3); + check_ns_list(ns3, &x1, &x2, &x3, &x4); + UCX_TEST_ASSERT(!err3, "ns3 failed"); + UCX_TEST_ASSERT(ns3, "ns3: no list"); + UCX_TEST_ASSERT(x1, "ns3: x1 missing"); + UCX_TEST_ASSERT(x2, "ns3: x2 missing"); + UCX_TEST_ASSERT(!x3, "ns3: x3"); + UCX_TEST_ASSERT(!x4, "ns3: x4"); + + xmlFreeDoc(doc3); + xmlFreeDoc(doc4); + xmlFreeDoc(doc5); + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_wsxml_write_nodes) { + Session *sn = testutil_session(); + TestIOStream *st = testutil_iostream(2048, TRUE); + + UCX_TEST_BEGIN; + xmlDoc *doc = xmlReadMemory( + XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0); + UCX_TEST_ASSERT(doc, "xml parser error"); + xmlNode *root = xmlDocGetRootElement(doc); + + Writer writer; + char buffer[1024]; + writer_init(&writer, st, buffer, 1024); + + int err = wsxml_write_nodes(sn->pool, &writer, NULL, root); + writer_flush(&writer); + UCX_TEST_ASSERT(err == 0, "wsxml_write_nodes error"); + UCX_TEST_ASSERT(st->buf->pos > 0, "buffer is empty"); + + //printf("\n\n"); + //printf("%.*s\n", (int)st->buf->size, st->buf->space); + //printf("\n\n"); + + xmlDoc *genDoc = xmlReadMemory( + st->buf->space, st->buf->size, NULL, NULL, 0); + UCX_TEST_ASSERT(genDoc, "generated doc is not valid xml"); + + xmlFreeDoc(doc); + xmlFreeDoc(genDoc); + + UCX_TEST_END; + testutil_iostream_destroy(st); + testutil_destroy_session(sn); +} + +UCX_TEST(test_wsxml_nslist2string) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + WSNamespace ns1; + WSNamespace ns2; + WSNamespace ns3; + memset(&ns1, 0, sizeof(WSNamespace)); + memset(&ns2, 0, sizeof(WSNamespace)); + memset(&ns3, 0, sizeof(WSNamespace)); + ns1.prefix = (const xmlChar*)"x"; + ns1.href = (const xmlChar*)"ns1"; + ns2.prefix = (const xmlChar*)"y"; + ns2.href = (const xmlChar*)"ns2"; + ns3.prefix = (const xmlChar*)"z"; + ns3.href = (const xmlChar*)"ns3"; + + WebdavNSList elm1 = { &ns1, NULL, NULL }; + WebdavNSList elm2 = { &ns2, NULL, NULL }; + WebdavNSList elm3 = { &ns3, NULL, NULL }; + + // single elm test + char *str1 = wsxml_nslist2string(sn->pool, &elm1); + UCX_TEST_ASSERT(str1, "str1 is null"); + UCX_TEST_ASSERT(!strcmp(str1, "x:ns1"), "str1: wrong content"); + + // 2 elm test + elm1.next = &elm2; + char *str2 = wsxml_nslist2string(sn->pool, &elm1); + UCX_TEST_ASSERT(str2, "str2 is null"); + UCX_TEST_ASSERT(!strcmp(str2, "x:ns1\ny:ns2"), "str2: wrong content"); + + // 3 elm test + elm2.next = &elm3; + char *str3 = wsxml_nslist2string(sn->pool, &elm1); + UCX_TEST_ASSERT(str3, "str3 is null"); + UCX_TEST_ASSERT(!strcmp(str3, "x:ns1\ny:ns2\nz:ns3"), "str3: wrong content"); + + // empty prefix test + ns1.prefix = NULL; + char *str4 = wsxml_nslist2string(sn->pool, &elm1); + UCX_TEST_ASSERT(str4, "str3 is null"); + UCX_TEST_ASSERT(!strcmp(str4, ":ns1\ny:ns2\nz:ns3"), "str4: wrong content"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_wsxml_string2nslist) { + Session *sn = testutil_session(); + + UCX_TEST_BEGIN; + + // empty list + WebdavNSList *list1 = wsxml_string2nslist(sn->pool, ""); + UCX_TEST_ASSERT(!list1, "list1 should be NULL"); + + // 1 elm list + WebdavNSList *list2 = wsxml_string2nslist(sn->pool, "x:ns1"); + UCX_TEST_ASSERT(list2, "list2 is NULL"); + UCX_TEST_ASSERT(list2->namespace, "list2 namespace is NULL"); + UCX_TEST_ASSERT(!strcmp((const char*)list2->namespace->prefix, "x"), "list2: wrong prefix"); + UCX_TEST_ASSERT(!strcmp((const char*)list2->namespace->href, "ns1"), "list2: wrong href"); + + // 2 elm list + WebdavNSList *list3 = wsxml_string2nslist(sn->pool, "x:ns1\ny:ns2"); + UCX_TEST_ASSERT(list3, "list3 is NULL"); + UCX_TEST_ASSERT(list3->namespace, "list3 namespace is NULL"); + UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->prefix, "x"), "list3: wrong prefix"); + UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: wrong href"); + UCX_TEST_ASSERT(list3->next, "list3 elm2 is NULL"); + UCX_TEST_ASSERT(list3->next->namespace, "list3 namespace 2 is NULL"); + UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->prefix, "x"), "list3: elm2 wrong prefix"); + UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: elm2 wrong href"); + + // empty prefix + WebdavNSList *list4 = wsxml_string2nslist(sn->pool, ":x\ny:ns2"); + UCX_TEST_ASSERT(list4, "list4 is NULL"); + UCX_TEST_ASSERT(list4->namespace, "list4 namespace is NULL"); + UCX_TEST_ASSERT(!list4->namespace->prefix, "list4 elm1 prefix should be NULL"); + UCX_TEST_ASSERT(!strcmp((const char*)list3->namespace->href, "ns1"), "list3: wrong href"); + + + UCX_TEST_END; + testutil_destroy_session(sn); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/test/xml.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/xml.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,133 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_XML_H +#define TEST_XML_H + +#include "../public/nsapi.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +UCX_TEST(test_wsxml_iterator); +UCX_TEST(test_wsxml_get_required_namespaces); +UCX_TEST(test_wsxml_write_nodes); +UCX_TEST(test_wsxml_nslist2string); +UCX_TEST(test_wsxml_string2nslist); + + +#define XML_TESTDATA1 " \ + \ + teststr \ + \ + \ + \ + hello \ + world\ + \ + \ + \ + " + +#define XML_TESTDATA2 " \ + \ + teststr \ + \ + \ + \ + hello \ + world\ + \ + \ + \ + " + +#define XML_TESTDATA3 " \ + \ + str1\ + str1\ + str1\ + str1\ + " + +#define XML_TESTDATA4 " \ + \ + str1\ + str1\ + " + +#define XML_TESTDATA5 " \ + \ + str1\ + str1\ + str1\ + str1\ + " + +#define XML_TESTDATA6 " \n\ + \n\ + str1\n\ + str1\n\ + str1\n\ + \n\ + \n\ + text\n\ + \n\ + \n\ + \n\ + \n\ + Hello\n\ + World\n\ + end.\n\ + \n\ + \n\ + entity reference test &quote& \n\ + <xml>\n\ + \n\ + \n" + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_XML_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/util/io.c --- a/src/server/util/io.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/io.c Sat Sep 24 16:26:10 2022 +0200 @@ -252,12 +252,63 @@ st->fd = fd; st->max_read = 0; st->read = 0; + st->read_total = 0; + st->readbuf = NULL; + st->bufsize = 0; + st->buflen = NULL; + st->bufpos = NULL; + st->chunk_buf_pos = 0; st->chunked_enc = WS_FALSE; - st->buffered = WS_FALSE; + st->read_eof = WS_TRUE; + st->write_eof = WS_FALSE; return (IOStream*)st; } +int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos) { + if(st->read != (io_read_f)net_http_read) { + log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_read: IOStream is not an HttpStream"); + return 1; + } + st->read = (io_read_f)net_http_read_chunked; + HttpStream *http = (HttpStream*)st; + http->max_read = 0; + http->read = 0; + http->readbuf = buffer; + http->bufsize = bufsize; + http->buflen = cursize; + http->bufpos = pos; + http->chunk_buf_pos = 0; + http->read_eof = WS_FALSE; + return 0; +} + +int httpstream_enable_chunked_write(IOStream *st) { + if(st->write != (io_write_f)net_http_write) { + log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_write: IOStream is not an HttpStream"); + return 1; + } + HttpStream *http = (HttpStream*)st; + http->chunked_enc = WS_TRUE; + return 0; +} + +int httpstream_set_max_read(IOStream *st, int64_t maxread) { + if(st->write != (io_write_f)net_http_write) { + log_ereport(LOG_FAILURE, "%s", "httpstream_set_max_read: IOStream is not an HttpStream"); + return 1; + } + HttpStream *http = (HttpStream*)st; + http->max_read = maxread; + return 0; +} + +WSBool httpstream_eof(IOStream *st) { + HttpStream *http = (HttpStream*)st; + return http->read_eof; +} + ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) { + if(st->write_eof) return 0; IOStream *fd = st->fd; if(st->chunked_enc) { // TODO: on some plattforms iov_len is smaller than size_t @@ -269,17 +320,23 @@ io[1].iov_len = nbytes; io[2].iov_base = "\r\n"; io[2].iov_len = 2; + // TODO: FIXME: if r < sum of iov_len, everything would explode + // we need to store the chunk state and remaining bytes ssize_t r = fd->writev(fd, io, 3); - return r - io[0].iov_len; + return r - io[0].iov_len - io[2].iov_len; } else { return fd->write(fd, buf, nbytes); } } ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt) { + if(st->write_eof) return 0; IOStream *fd = st->fd; if(st->chunked_enc) { struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec)); + if(!io) { + return 0; + } char chunk_len[16]; io[0].iov_base = chunk_len; size_t len = 0; @@ -289,22 +346,247 @@ io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len); memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec)); ssize_t r = fd->writev(fd, io, iovcnt + 1); - return r - io[0].iov_len; + + ssize_t ret = r - io[0].iov_len; + free(io); + return ret; } else { return fd->writev(fd, iovec, iovcnt); } } ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes) { - if(st->max_read != 0 && st->read >= st->max_read) { + if(st->read >= st->max_read) { + st->read_eof = WS_TRUE; return 0; } ssize_t r = st->fd->read(st->fd, buf, nbytes); + if(r < 0) { + st->st.io_errno = st->fd->io_errno; + } st->read += r; return r; } -ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) { +#define BUF_UNNEEDED_DIFF 64 +/* + * read from st->chunk_buf first, read from st->fd if perform_io is true + */ +static ssize_t net_http_read_buffered(HttpStream *st, char *buf, size_t nbytes, WSBool read_data, WSBool *perform_io) { + ssize_t r = 0; + + //memset(buf, 'x', nbytes); + //char *orig_buf = buf; + + // copy available data from st->readbuf to buf + int pos = *st->bufpos; + size_t buf_available = *st->buflen - pos; + if(buf_available) { + size_t cplen = buf_available > nbytes ? nbytes : buf_available; + if(read_data) { + // if we read data (and not a chunk header), we limit the + // amount of bytes we copy + size_t chunk_available = st->max_read - st->read; + cplen = cplen > chunk_available ? chunk_available : cplen; + st->read += cplen; + } + memcpy(buf, st->readbuf + pos, cplen); + *st->bufpos += cplen; + r += cplen; + buf += cplen; + nbytes -= cplen; + } + + // maybe perform IO and refill the read buffer + // if we read data (read_data == true), make sure not to perform IO, + // when a chunk is completed + // + // if we read a chunk header (read_data == false) it is very important + // to not perform IO, if we have previously copied data from readbuf + // this ensures we never override non-chunk-header data + if(*perform_io && ((read_data && nbytes > 0 && st->max_read - st->read) || (!read_data && r == 0))) { + if(*st->buflen - *st->bufpos > 0) { + printf("todo: fix, should not happen, remove later\n"); + } + // fill buffer again + ssize_t rlen = st->fd->read(st->fd, st->readbuf, st->bufsize); + *st->buflen = rlen; + *st->bufpos = 0; + *perform_io = WS_FALSE; + if(rlen < 0) { + st->st.io_errno = st->fd->io_errno; + } + + if(rlen > 0) { + // call func again to get data from buffer (no IO will be performed) + r += net_http_read_buffered(st, buf, nbytes, read_data, perform_io); + } + } + + return r; +} + + +/* + * parses a chunk header + * the chunk length is stored in chunklen + * return: 0 if the data is incomplete + * -1 if an error occured + * >0 chunk header length + */ +static int parse_chunk_header(char *str, int len, WSBool first, int64_t *chunklen) { + char *hdr_start = NULL; + char *hdr_end = NULL; + int i = 0; + if(first) { + hdr_start = str; + } else { + if(len < 3) { + return 0; + } + if(str[0] == '\r' && str[1] == '\n') { + hdr_start = str+2; + i = 2; + } else if(str[0] == '\n') { + hdr_start = str+1; + i = 1; + } else { + return -1; + } + } + + for(;i= len) { + return 0; + } + if(str[i] == '\n') { + i++; + } else if(str[i] == '\r') { + if(++i >= len) { + return 0; + } + if(str[i] == '\n') { + i++; + } else { + return -1; + } + } else { + return -1; + } + } + + *chunklen = clen; + return i; +} + +ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes) { + if(st->read_eof) { + return 0; + } + + char *rbuf = buf; // buffer pos + size_t rd = 0; // number of bytes read + size_t rbuflen = nbytes; // number of bytes until end of buf + WSBool perform_io = WS_TRUE; // we do only 1 read before we abort + while(rd < nbytes && (perform_io || (st->max_read - st->read) > 0)) { + // how many bytes are available in the current chunk + size_t chunk_available = st->max_read - st->read; + if(chunk_available > 0) { + ssize_t r = net_http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io); + if(r == 0) { + break; + } + rd += r; + st->read_total += r; + rbuf += r; + rbuflen -= r; + } else { + int chunkbuf_avail = HTTP_STREAM_CBUF_SIZE - st->chunk_buf_pos; + if(chunkbuf_avail == 0) { + // for some reason HTTP_STREAM_CBUF_SIZE is not enough + // to store the chunk header + // this indicates that something has gone wrong (or this is an attack) + st->read_eof = WS_TRUE; + return -1; + } + // fill st->chunk_buf + ssize_t r = net_http_read_buffered(st, &st->chunk_buf[st->chunk_buf_pos], chunkbuf_avail, FALSE, &perform_io); + if(r == 0) { + break; + } + int chunkbuf_len = st->chunk_buf_pos + r; + int64_t chunklen; + int ret = parse_chunk_header(st->chunk_buf, chunkbuf_len, st->read_total > 0 ? FALSE : TRUE, &chunklen); + if(ret == 0) { + // incomplete chunk header + st->chunk_buf_pos = chunkbuf_len; + } else if(ret < 0) { + // error + st->read_eof = WS_TRUE; + return -1; + } else if(ret > 0) { + st->max_read = chunklen; + st->read = 0; + int remaining_len = chunkbuf_len - ret; + if(remaining_len > 0) { + // we have read more into chunk_buf than the chunk_header + // it is save to just move bufpos back + *st->bufpos -= remaining_len; + } + //st->remaining_len = chunkbuf_len - ret; + st->chunk_buf_pos = 0; + + if(chunklen == 0) { + st->read_eof = WS_TRUE; + break; + } + } + } + + if(!perform_io && rd == 0) { + perform_io = WS_TRUE; + } + } + + return rd; +} + +ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) { + if(st->write_eof) return 0; ssize_t ret = 0; // TODO: support chunked transfer encoding if(st->fd->sendfile) { @@ -321,9 +603,10 @@ } void net_http_finish(HttpStream *st) { - if(st->chunked_enc) { + if(st->chunked_enc && !st->write_eof) { st->fd->write(st->fd, "0\r\n\r\n", 5); } + st->write_eof = WS_TRUE; } void net_http_setmode(HttpStream *st, int mode) { @@ -452,7 +735,7 @@ va_list arg; va_start(arg, format); sstr_t buf = ucx_vasprintf(ucx_default_allocator(), format, arg); - ssize_t r = net_write(fd, buf.ptr, buf.length); + ssize_t r = buf.length > 0 ? net_write(fd, buf.ptr, buf.length) : 0; free(buf.ptr); va_end(arg); if(r < 0) { diff -r 21274e5950af -r a1f4cb076d2f src/server/util/io.h --- a/src/server/util/io.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/io.h Sat Sep 24 16:26:10 2022 +0200 @@ -89,13 +89,59 @@ #endif }; +#define HTTP_STREAM_CBUF_SIZE 16 struct HttpStream { IOStream st; IOStream *fd; + + /* + * content-length or current chunk size + */ uint64_t max_read; + /* + * total bytes read (with content-length) or bytes read of current chunk + */ uint64_t read; + /* + * total bytes read with chunked transfer encoding + */ + uint64_t read_total; + /* + * read buffer (used only with chunked transfer encoding) + */ + char *readbuf; + /* + * readbuf allocated size + */ + size_t bufsize; + /* + * number of bytes currently stored in readbuf + */ + int *buflen; + /* + * current position in the read buffer + */ + int *bufpos; + /* + * current chunk_buf position + */ + int chunk_buf_pos; + /* + * buffer used only for parsing chunk headers + */ + char chunk_buf[HTTP_STREAM_CBUF_SIZE]; + /* + * chunked transfer encoding for write enabled? + */ WSBool chunked_enc; - WSBool buffered; + /* + * end of file indicator (read) + */ + WSBool read_eof; + /* + * end of file indicator (write) + */ + WSBool write_eof; }; typedef struct SSLStream { @@ -120,9 +166,15 @@ /* http stream */ IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd); +int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos); +int httpstream_enable_chunked_write(IOStream *st); +int httpstream_set_max_read(IOStream *st, int64_t maxread); +WSBool httpstream_eof(IOStream *st); + ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes); ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt); ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes); +ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes); ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd); void net_http_close(HttpStream *st); void net_http_finish(HttpStream *st); diff -r 21274e5950af -r a1f4cb076d2f src/server/util/netbuf.c --- a/src/server/util/netbuf.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/netbuf.c Sat Sep 24 16:26:10 2022 +0200 @@ -141,9 +141,15 @@ buf->pos += bytes_in_buffer; return bytes_in_buffer; + } else if(buf->pos >= buf->maxsize) { + return NETBUF_EOF; } } - + + if(!buf->sd) { + return NETBUF_EOF; + } + /* The netbuf is empty. Read data directly into the caller's buffer */ bytes = net_read(buf->sd, buffer, size); if (bytes == 0) diff -r 21274e5950af -r a1f4cb076d2f src/server/util/object.c --- a/src/server/util/object.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/object.c Sat Sep 24 16:26:10 2022 +0200 @@ -38,7 +38,6 @@ httpd_object* object_new(pool_handle_t *pool, char *name) { - // TODO: Speicherverwaltung httpd_object *obj = pool_malloc(pool, sizeof(httpd_object)); obj->pool = pool; obj->name = name; diff -r 21274e5950af -r a1f4cb076d2f src/server/util/objs.mk --- a/src/server/util/objs.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/objs.mk Sat Sep 24 16:26:10 2022 +0200 @@ -42,6 +42,7 @@ UTILOBJ += thrpool.o UTILOBJ += util.o UTILOBJ += date.o +UTILOBJ += writer.o UTILOBJS = $(UTILOBJ:%=$(UTIL_OBJPRE)%) UTILSOURCE = $(UTILOBJ:%.o=util/%.c) diff -r 21274e5950af -r a1f4cb076d2f src/server/util/pblock.cpp --- a/src/server/util/pblock.cpp Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/pblock.cpp Sat Sep 24 16:26:10 2022 +0200 @@ -324,7 +324,12 @@ const pb_key *const pb_key_vary = _create_key("vary"); const pb_key *const pb_key_via = _create_key("via"); const pb_key *const pb_key_warning = _create_key("warning"); - +const pb_key *const pb_key_depth = _create_key("depth"); +const pb_key *const pb_key_if = _create_key("if"); +const pb_key *const pb_key_vfs = _create_key("vfs"); +const pb_key *const pb_key_dav = _create_key("dav"); +const pb_key *const pb_key_vfsclass = _create_key("vfsclass"); +const pb_key *const pb_key_davclass = _create_key("davclass"); /* ------------------------------ _find_key ------------------------------- */ diff -r 21274e5950af -r a1f4cb076d2f src/server/util/pblock.h --- a/src/server/util/pblock.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/pblock.h Sat Sep 24 16:26:10 2022 +0200 @@ -301,6 +301,12 @@ extern const pb_key *const pb_key_vary; extern const pb_key *const pb_key_via; extern const pb_key *const pb_key_warning; +extern const pb_key *const pb_key_depth; +extern const pb_key *const pb_key_if; +extern const pb_key *const pb_key_vfs; +extern const pb_key *const pb_key_dav; +extern const pb_key *const pb_key_vfsclass; +extern const pb_key *const pb_key_davclass; NSAPI_PUBLIC pool_handle_t *pblock_pool(pblock *pb); diff -r 21274e5950af -r a1f4cb076d2f src/server/util/platform.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/platform.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BASE_PLATFORM_H +#define BASE_PLATFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__clang__) +#define WS_FALLTHROUGH [[clang::fallthrough]] +#elif defined(__GNUC__) +#define WS_FALLTHROUGH __attribute__((fallthrough)) +#else +#define WS_FALLTHROUGH +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* BASE_PLATFORM_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/util/pool.c --- a/src/server/util/pool.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/pool.c Sat Sep 24 16:26:10 2022 +0200 @@ -65,6 +65,7 @@ #include #include +#include //define PERM_MALLOC malloc //define PERM_FREE free //define PERM_REALLOC realloc @@ -91,6 +92,8 @@ return 0; } +#define POOL_MIN_BLOCKSIZE 128 + NSAPI_PUBLIC int pool_init(pblock *pb, Session *sn, Request *rq) { @@ -101,11 +104,22 @@ int n; //printf("standard block size: %d\n", pool_config.block_size); - + if (str_block_size != NULL) { - n = atoi(str_block_size); - if (n > 0) - pool_config.block_size = n; + int64_t value; + if(!util_strtoint(str_block_size, &value)) { + log_ereport(LOG_MISCONFIG, "pool-init: param 'block-size' is not an integer"); + return REQ_ABORTED; + } + if(value > INT_MAX) { + log_ereport(LOG_MISCONFIG, "pool-init: block-size is too big"); + return REQ_ABORTED; + } + if(value < POOL_MIN_BLOCKSIZE) { + log_ereport(LOG_MISCONFIG, "pool-init: block-size is too small"); + return REQ_ABORTED; + } + pool_config.block_size = value; } if (str_pool_disable && util_getboolean(str_pool_disable, PR_TRUE)) { diff -r 21274e5950af -r a1f4cb076d2f src/server/util/systems.h --- a/src/server/util/systems.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/systems.h Sat Sep 24 16:26:10 2022 +0200 @@ -58,10 +58,6 @@ typedef int PRBool; #define PR_TRUE 1 #define PR_FALSE 0 - -typedef int WSBool; -#define WS_TRUE 1 -#define WS_FALSE 0 /* end new types */ /* --- End common definitions for all supported platforms --- */ diff -r 21274e5950af -r a1f4cb076d2f src/server/util/thrpool.c --- a/src/server/util/thrpool.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/thrpool.c Sat Sep 24 16:26:10 2022 +0200 @@ -47,18 +47,21 @@ pthread_mutex_init(&pool->queue_lock, NULL); pthread_mutex_init(&pool->avlbl_lock, NULL); - pthread_cond_init(&pool->available, NULL); + pthread_cond_init(&pool->available, NULL); + return pool; +} + +int threadpool_start(threadpool_t *pool) { /* create pool threads */ - for(int i=0;imin_threads;i++) { pthread_t t; if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { - perror("Error: threadpool_new: pthread_create"); - return NULL; + perror("Error: threadpool_start: pthread_create"); + return 1; } } - - return pool; + return 0; } void* threadpool_func(void *data) { @@ -103,6 +106,12 @@ } void threadpool_run(threadpool_t *pool, job_callback_f func, void *data) { + // TODO: handle errors + + if(pool->num_threads == 0) { + threadpool_start(pool); + } + threadpool_job *job = malloc(sizeof(threadpool_job)); job->callback = func; job->data = data; diff -r 21274e5950af -r a1f4cb076d2f src/server/util/uri.cpp --- a/src/server/util/uri.cpp Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/uri.cpp Sat Sep 24 16:26:10 2022 +0200 @@ -57,7 +57,7 @@ } return flagDbcsUri; */ - return PR_FALSE; + return PR_TRUE; } #ifdef XP_WIN32 @@ -228,7 +228,6 @@ /* --------------------------- util_uri_escape ---------------------------- */ -/* NSAPI_PUBLIC char *util_uri_escape(char *od, const char *s) { int flagDbcsUri = allow_dbcs_uri(); @@ -267,11 +266,10 @@ *d = '\0'; return od; } -*/ /* --------------------------- util_url_escape ---------------------------- */ -/* + NSAPI_PUBLIC char *util_url_escape(char *od, const char *s) { int flagDbcsUri = allow_dbcs_uri(); @@ -310,7 +308,7 @@ *d = '\0'; return od; } -*/ + /* ------------------------- util_uri_strip_params ------------------------- */ diff -r 21274e5950af -r a1f4cb076d2f src/server/util/util.c --- a/src/server/util/util.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/util.c Sat Sep 24 16:26:10 2022 +0200 @@ -55,6 +55,7 @@ #include "../public/nsapi.h" #include #include +#include #include "pblock.h" #include "util.h" @@ -288,6 +289,24 @@ return vsnprintf(s, n, fmt, args); } +NSAPI_PUBLIC int util_vasprintf(pool_handle_t *pool, char **s, const char *fmt, + va_list args) +{ + UcxAllocator a = util_pool_allocator(pool); + va_list ap; + va_copy(ap, args); + sstr_t str = ucx_vasprintf(&a, fmt, ap); + *s = str.ptr; + return str.length; +} + +NSAPI_PUBLIC int util_asprintf(pool_handle_t *pool, char **s, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + return util_vasprintf(pool, s, fmt, args); +} + NSAPI_PUBLIC int util_vsprintf(char *s, register const char *fmt, va_list args) { return vsprintf(s, fmt, args); @@ -394,7 +413,7 @@ return def; } -int util_getboolean_s(sstr_t s, int def) { +int util_getboolean_s(scstr_t s, int def) { if(s.length == 0) { return def; } @@ -407,7 +426,7 @@ return def; } -NSAPI_PUBLIC int util_strtoint(char *str, int64_t *value) { +NSAPI_PUBLIC int util_strtoint(const char *str, int64_t *value) { char *end; errno = 0; int64_t val = strtoll(str, &end, 0); @@ -419,6 +438,41 @@ } } +NSAPI_PUBLIC const char* util_resource_name(const char *url) { + scstr_t urlstr = scstr(url); + if(urlstr.ptr[urlstr.length-1] == '/') { + urlstr.length--; + } + scstr_t resname = scstrrchr(urlstr, '/'); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +NSAPI_PUBLIC char* util_parent_path(const char *path) { + char *name = (char*)util_resource_name((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + char *parent = MALLOC(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +NSAPI_PUBLIC char* util_parent_path_pool(pool_handle_t *pool, const char *path) { + // maybe we can unify this function with util_parent_path + char *name = (char*)util_resource_name((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + char *parent = pool_malloc(pool, parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} /* ------------------------------ util_itoa ------------------------------- */ /* @@ -1033,3 +1087,97 @@ return pos; } + + +/* ------------------------- util_html_escape -------------------------- */ + +NSAPI_PUBLIC char *util_html_escape(const char *s) +{ + const char *in; + + int len = 0; + for (in = s; *in; in++) { + switch (*in) { + case '<': + len += 4; // < + break; + case '>': + len += 4; // > + break; + case '&': + len += 5; // & + break; + case '"': + len += 6; // " + break; + case '\'': + len += 6; // ' + break; + case '+': + len += 5; // + + break; + default: + len++; + break; + } + } + + char *ns = (char *) MALLOC(len + 1); + if (!ns) + return ns; + + char *out = ns; + for (in = s; *in; in++) { + switch (*in) { + case '<': + *out++ = '&'; + *out++ = 'l'; + *out++ = 't'; + *out++ = ';'; + break; + case '>': + *out++ = '&'; + *out++ = 'g'; + *out++ = 't'; + *out++ = ';'; + break; + case '&': + *out++ = '&'; + *out++ = 'a'; + *out++ = 'm'; + *out++ = 'p'; + *out++ = ';'; + break; + case '"': + *out++ = '&'; + *out++ = 'q'; + *out++ = 'u'; + *out++ = 'o'; + *out++ = 't'; + *out++ = ';'; + break; + case '\'': + *out++ = '&'; + *out++ = 'a'; + *out++ = 'p'; + *out++ = 'o'; + *out++ = 's'; + *out++ = ';'; + break; + case '+': + *out++ = '&'; + *out++ = '#'; + *out++ = '4'; + *out++ = '3'; + *out++ = ';'; + break; + default: + *out++ = *in; + break; + } + } + *out = '\0'; + + return ns; +} + diff -r 21274e5950af -r a1f4cb076d2f src/server/util/util.h --- a/src/server/util/util.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/util/util.h Sat Sep 24 16:26:10 2022 +0200 @@ -170,6 +170,11 @@ NSAPI_PUBLIC int INTutil_snprintf(char *s, int n, const char *fmt, ...); +NSAPI_PUBLIC int util_vasprintf(pool_handle_t *pool, char **s, const char *fmt, + va_list args); + +NSAPI_PUBLIC int util_asprintf(pool_handle_t *pool, char **s, const char *fmt, ...); + NSAPI_PUBLIC int util_strlftime(char *dst, size_t dstsize, const char *format, const struct tm *t); NSAPI_PUBLIC int INTutil_strftime(char *s, const char *format, const struct tm *t); @@ -197,10 +202,13 @@ NSAPI_PUBLIC PRBool INTutil_format_http_version(const char *v, int *protv_num, char *buffer, int size); NSAPI_PUBLIC int INTutil_getboolean(const char *v, int def); -int util_getboolean_s(sstr_t s, int def); +int util_getboolean_s(scstr_t s, int def); // new -NSAPI_PUBLIC int util_strtoint(char *str, int64_t *value); +NSAPI_PUBLIC int util_strtoint(const char *str, int64_t *value); +NSAPI_PUBLIC const char* util_resource_name(const char *url); +NSAPI_PUBLIC char* util_parent_path(const char *path); +NSAPI_PUBLIC char* util_parent_path_pool(pool_handle_t *pool, const char *path); // TODO //NSAPI_PUBLIC PRIntervalTime INTutil_getinterval(const char *v, PRIntervalTime def); @@ -229,8 +237,6 @@ NSAPI_PUBLIC int64_t util_atoi64(const char *a); -NSAPI_PUBLIC char *util_html_escape(const char *s); - NSAPI_PUBLIC int util_qtoi(const char *q, const char **p); /* path utils */ @@ -331,6 +337,8 @@ #define util_sprintf INTutil_sprintf #define util_vsnprintf INTutil_vsnprintf #define util_snprintf INTutil_snprintf +#define util_vasprintf INTutil_vasprintf +#define util_asprintf INTutil_asprintf #define util_strftime INTutil_strftime #define util_strcasecmp INTutil_strcasecmp #define util_strncasecmp INTutil_strncasecmp diff -r 21274e5950af -r a1f4cb076d2f src/server/util/writer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/writer.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,124 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "writer.h" + +void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len) { + w->fd = fd; + w->write = (wr_writefunc)net_write; + w->buffer = buf; + w->size = len; + w->pos = 0; + w->error = 0; +} + +void writer_init_with_stream(Writer *w, void *stream, wr_writefunc writefunc, char *buf, size_t len) { + w->fd = stream; + w->write = writefunc; + w->buffer = buf; + w->size = len; + w->pos = 0; + w->error = 0; +} + +int writer_flush(Writer *w) { + if(w->error) { + return w->error; + } + + size_t pos = 0; + size_t len = w->pos; + + while(len > 0) { + //fwrite(w->buffer+pos, 1, len, stdout); + //fflush(stdout); + ssize_t r = w->write(w->fd, w->buffer + pos, len); + if(r <= 0) { + break; + } + len -= r; + pos += r; + } + + if(pos != w->pos) { + w->error = 1; + return 1; + } + + w->pos = 0; + return 0; +} + +int writer_put(Writer *w, const char *s, size_t len) { + if(w->error) { + return w->error; + } + + // available bytes + size_t a = w->size - w->pos; + if(a == 0) { + if(writer_flush(w)) { + return 1; + } + } + + size_t cplen = len > a ? a : len; // number of bytes we can copy + memcpy(w->buffer+w->pos, s, cplen); + w->pos += cplen; + + if(cplen < len) { + // not all bytes copied -> call writer_put again + // the number of available bytes is 0 then, therefore flush is called + return writer_put(w, s + cplen, len - cplen); + } else { + return 0; + } +} + +int writer_puts(Writer *w, sstr_t s) { + return writer_put(w, s.ptr, s.length); +} + +int writer_putc(Writer *w, char c) { + if(w->pos == w->size) { + if(writer_flush(w)) { + return 1; + } + } + w->buffer[w->pos++] = c; + return 0; +} + +int writer_fwrite(const void *s, size_t size, size_t nelem, Writer *out) { + int w = writer_put(out, s, size*nelem); + return w/size; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/util/writer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/writer.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,71 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WRITER_H +#define WRITER_H + +#include "../public/nsapi.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef ssize_t (*wr_writefunc)(void *, const char *, size_t); + +typedef struct Writer { + void *fd; + wr_writefunc write; + char *buffer; + size_t size; + size_t pos; + int error; +} Writer; + + + +void writer_init(Writer *w, SYS_NETFD fd, char *buf, size_t len); + +void writer_init_with_stream(Writer *w, void *stream, wr_writefunc writefunc, char *buf, size_t len); + +int writer_flush(Writer *w); + +int writer_put(Writer *w, const char *s, size_t len); + +int writer_puts(Writer *w, sstr_t s); + +int writer_putc(Writer *w, char c); + +int writer_fwrite(const void *s, size_t size, size_t nelem, Writer *w); + +#ifdef __cplusplus +} +#endif + +#endif /* WRITER_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/multistatus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,687 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2020 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "../daemon/session.h" +#include "../daemon/protocol.h" +#include "../util/platform.h" + +#include + +#include "multistatus.h" + +#include "operation.h" +#include "xml.h" + +#define MULTISTATUS_BUFFER_LENGTH 2048 + +Multistatus* multistatus_response(Session *sn, Request *rq) { + Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus)); + if(!ms) { + return NULL; + } + ZERO(ms, sizeof(Multistatus)); + ms->response.addresource = multistatus_addresource; + ms->sn = sn; + ms->rq = rq; + ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8); + ms->proppatch = FALSE; + if(!ms->namespaces) { + return NULL; + } + if(ucx_map_cstr_put(ms->namespaces, "D", webdav_dav_namespace())) { + return NULL; + } + return ms; +} + +static int send_xml_root(Multistatus *ms, Writer *out) { + writer_puts(out, S("\n" + "namespaces); + WSNamespace *ns; + UCX_MAP_FOREACH(key, ns, i) { + writer_puts(out, S(" xmlns:")); + writer_put(out, key.data, key.len); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)ns->href)); + writer_puts(out, S("\"")); + } + + writer_puts(out, S(">\n")); + + return out->error; +} + +static void send_nsdef(WSNamespace *ns, Writer *out) { + writer_puts(out, S(" xmlns:")); + writer_puts(out, sstr((char*)ns->prefix)); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)ns->href)); + writer_putc(out, '\"'); +} + +static void send_string_escaped(Writer *out, sstr_t str) { + char *begin = str.ptr; + char *end = begin; + char *escape = NULL; + int esclen; + for(size_t i=0;i': { + escape = ">"; + esclen = 4; + break; + } + default: continue; + } + ptrdiff_t len = end - begin; + if(len > 0) { + writer_put(out, begin, len); + begin = end + 1; + } + writer_put(out, escape, esclen); + } + ptrdiff_t len = end - begin; + if(len > 0) { + writer_put(out, begin, len + 1); + begin = end + 1; + } +} + +static int send_property( + Multistatus *ms, + WebdavProperty *property, + WebdavNSList *nsdef, + WSBool writeContent, + Writer *out) +{ + // write: "namespace->prefix)); + writer_putc(out, ':'); + writer_puts(out, sstr((char*)property->name)); + + // check if the namespace is already defined + WSBool need_nsdef = TRUE; + WSNamespace *ns = ucx_map_cstr_get( + ms->namespaces, + (char*)property->namespace->prefix); + if(ns && !strcmp( + (const char*)ns->href, + (const char*)property->namespace->href)) + { + need_nsdef = FALSE; // prefix and href are the same, no need for nsdef + } + + // send definition for the element's namespace + if(need_nsdef) { + send_nsdef(property->namespace, out); + } + + // send additional namespace definitions required for the value + WebdavNSList *def = nsdef; + while(def) { + send_nsdef(def->namespace, out); + def = def->next; + } + + // send xml lang attribute + if(property->lang) { + writer_puts(out, S(" xml:lang=\"")); + writer_puts(out, sstr((char*)property->lang)); + writer_putc(out, '\"'); + } + + // end property tag and write content + if(writeContent) { + writer_putc(out, '>'); + + // content + switch(property->vtype) { + case WS_VALUE_NO_TYPE: break; + case WS_VALUE_XML_NODE: { + wsxml_write_nodes_without_nsdef( + ms->sn->pool, + out, + property->value.node); + break; + } + case WS_VALUE_XML_DATA: { + // only write data, data->namespaces is already handled + writer_put( + out, + property->value.data.data, + property->value.data.length); + break; + } + case WS_VALUE_TEXT: { + // asume the text is already escaped + writer_put( + out, + property->value.text.str, + property->value.text.length); + break; + } + } + + // end tag + writer_puts(out, S("namespace->prefix)); + writer_putc(out, ':'); + writer_puts(out, sstr((char*)property->name)); + writer_putc(out, '>'); + } else { + writer_puts(out, S("/>")); + } + + return out->error; +} + +static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) { + writer_puts(out, S(" \n" + " ")); + //writer_puts(out, sstr(rp->resource.href)); + send_string_escaped(out, sstr(rp->resource.href)); + writer_puts(out, S("\n")); + + WSBool writeContent = ms->proppatch ? FALSE : TRUE; + + if(rp->plist_begin) { + writer_puts(out, S(" \n" + " \n")); + // send properties + PropertyOkList *p = rp->plist_begin; + while(p) { + writer_puts(out, S(" ")); + if(send_property(ms, p->property, p->nsdef, writeContent, out)) { + return out->error; + } + writer_puts(out, S("\n")); + p = p->next; + } + + writer_puts(out, S(" \n" + " HTTP/1.1 200 OK\n" + " \n")); + } + + // send error properties + PropertyErrorList *error = rp->errors; + while(error) { + writer_puts(out, S(" \n" + " \n")); + + WebdavPList *errprop = error->begin; + while(errprop) { + writer_puts(out, S(" ")); + if(send_property(ms, errprop->property, NULL, FALSE, out)) { + return out->error; + } + writer_putc(out, '\n'); + errprop = errprop->next; + } + + char statuscode[8]; + int sclen = snprintf(statuscode, 8, "%d ", error->status); + if(sclen > 4) { + statuscode[0] = '5'; + statuscode[1] = '0'; + statuscode[2] = '0'; + statuscode[3] = ' '; + sclen = 4; + } + writer_puts(out, S(" \n" + " HTTP/1.1 ")); + writer_put(out, statuscode, sclen); + const char *status_msg = protocol_status_message(error->status); + if(status_msg) { + writer_put(out, status_msg, strlen(status_msg)); + } else { + writer_puts(out, S("Server Error")); + } + writer_puts(out, S("\n" + " \n")); + + + error = error->next; + } + + // end response tag + writer_puts(out, S(" \n")); + + return out->error; +} + +int multistatus_send(Multistatus *ms, SYS_NETFD net) { + // make sure every resource is closed + if(ms->current && !ms->current->resource.isclosed) { + if(msresponse_close((WebdavResource*)ms->current)) { + return 1; + } + } + + // start http response + protocol_status(ms->sn, ms->rq, 207, NULL); + protocol_start_response(ms->sn, ms->rq); + + char buffer[MULTISTATUS_BUFFER_LENGTH]; + // create a writer, that flushes the buffer when it is filled + Writer writer; + Writer *out = &writer; + writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH); + + // send the xml root element with namespace defs + if(send_xml_root(ms, out)) { + return 1; + } + + // send response tags + MSResponse *response = ms->first; + while(response) { + if(send_response_tag(ms, response, out)) { + return 1; + } + response = response->next; + } + + // end multistatus + writer_puts(out, S("\n")); + + //printf("\n\n"); + //fflush(stdout); + + writer_flush(out); + + return 0; +} + +WebdavResource * multistatus_addresource( + WebdavResponse *response, + const char *path) +{ + Multistatus *ms = (Multistatus*)response; + MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse)); + if(!res) { + return NULL; + } + ZERO(res, sizeof(MSResponse)); + + // set href + res->resource.href = pool_strdup(ms->sn->pool, path); + if(!res->resource.href) { + return NULL; + } + + res->resource.err = 0; + + // add resource funcs + res->resource.addproperty = msresponse_addproperty; + res->resource.close = msresponse_close; + + res->properties = ucx_map_new_a(session_get_allocator(ms->sn), 32); + if(!res->properties) { + return NULL; + } + + res->multistatus = ms; + res->errors = NULL; + res->resource.isclosed = 0; + res->closing = 0; + + // add new resource to the resource list + if(ms->current) { + // before adding a new resource, the current resource must be closed + if(!ms->current->resource.isclosed) { + msresponse_close((WebdavResource*)ms->current); + } + ms->current->next = res; + } else { + ms->first = res; + } + ms->current = res; + + return (WebdavResource*)res; +} + +static int oklist_add( + pool_handle_t *pool, + PropertyOkList **begin, + PropertyOkList **end, + WebdavProperty *property, + WebdavNSList *nsdef) +{ + PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList)); + if(!newelm) { + return 1; + } + newelm->property = property; + newelm->nsdef = nsdef; + newelm->next = NULL; + if(*end) { + (*end)->next = newelm; + } else { + *begin = newelm; + } + *end = newelm; + return 0; +} + +int msresponse_addproperty( + WebdavResource *res, + WebdavProperty *property, + int status) +{ + MSResponse *response = (MSResponse*)res; + Session *sn = response->multistatus->sn; + if(response->resource.isclosed) { + log_ereport( + LOG_WARN, + "%s", + "webdav: cannot add property to closed response tag"); + return 0; + } + + // some WebdavProperty checks to make sure nothing explodes + if(!property->namespace || !property->namespace->href) { + // error: namespace is required + log_ereport( + LOG_FAILURE, + "%s", + "webdav: property '%s' has no namespace", + property->name); + return 1; + } + + // check if the property was already added to the resource + UcxAllocator *a = session_get_allocator(sn); + sstr_t key = sstrcat_a( + a, + 3, + sstr((char*)property->namespace->href), + S("\0"), + sstr((char*)property->name)); + if(ucx_map_sstr_get(response->properties, key)) { + a->free(a->pool, key.ptr); + return 0; + } + if(ucx_map_sstr_put(response->properties, key, property)) { + return 1; // OOM + } + a->free(a->pool, key.ptr); + + // list of namespace definitions for this property + WebdavNSList *nsdef_begin = NULL; + WebdavNSList *nsdef_end = NULL; + + // add namespace of this property to the namespace map + // the namespace map will be used for global namespace definitions + if(property->namespace->prefix) { + WSNamespace *ns = ucx_map_cstr_get( + response->multistatus->namespaces, + (const char*)property->namespace->prefix); + if(!ns) { + // prefix is not in use -> we can add the namespace to the ns map + int err = ucx_map_cstr_put( + response->multistatus->namespaces, + (const char*)property->namespace->prefix, + property->namespace); + if(err) { + return 1; // OOM + } + } else if( + strcmp((const char*)property->namespace->href, + (const char*)ns->href)) + { + // global namespace != local namespace + // therefore we need a namespace definition in this element + + // ns-prefix != property-prefix -> add ns to nsdef + if(webdav_nslist_add( + sn->pool, + &nsdef_begin, + &nsdef_end, + property->namespace)) + { + return 1; // OOM + } + } + } + + if(response->multistatus->proppatch && response->errors) { + // in a proppatch request all operations must succeed + // if we have an error, the property update status code must be + // 424 Failed Dependency + status = 424; + } + + // error properties will be added to a separate list + if(status != 200) { + return msresponse_addproperror(response, property, status); + } + + // add all namespaces used by this property to the nsdef list + WebdavNSList *nslist = NULL; + if(property->vtype == WS_VALUE_XML_NODE) { + // iterate over xml tree and collect all namespaces + int err = 0; + nslist = wsxml_get_required_namespaces( + response->multistatus->sn->pool, + property->value.node, + &err); + if(err) { + return 1; // OOM + } + } else if(property->vtype == WS_VALUE_XML_DATA) { + // xml data contains a list of all used namespaces + nslist = property->value.data.namespaces; + } // other value types don't contain xml namespaces + + while(nslist) { + // only add the namespace to the definitions list, if it isn't a + // property namespace, because the prop ns is already added + // to the element's def list or global definitions list + if(strcmp( + (const char*)nslist->namespace->prefix, + (const char*)property->namespace->prefix)) + { + // ns-prefix != property-prefix -> add ns to nsdef + if(webdav_nslist_add( + sn->pool, + &nsdef_begin, + &nsdef_end, + nslist->namespace)) + { + return 1; // OOM + } + } + nslist = nslist->next; + } + + // add property to the list + if(oklist_add( + sn->pool, + &response->plist_begin, + &response->plist_end, + property, + nsdef_begin)) + { + return 1; + } + return 0; +} + +int msresponse_addproperror( + MSResponse *response, + WebdavProperty *property, + int statuscode) +{ + pool_handle_t *pool = response->multistatus->sn->pool; + UcxAllocator *a = session_get_allocator(response->multistatus->sn); + + response->resource.err++; + + // MSResponse contains a list of properties for each status code + // at first find the list for this status code + PropertyErrorList *errlist = NULL; + PropertyErrorList *list = response->errors; + PropertyErrorList *last = NULL; + while(list) { + if(list->status == statuscode) { + errlist = list; + break; + } + last = list; + list = list->next; + } + + if(!errlist) { + // no list available for this statuscode + PropertyErrorList *newelm = pool_malloc(pool, + sizeof(PropertyErrorList)); + if(!newelm) { + return 1; + } + newelm->begin = NULL; + newelm->end = NULL; + newelm->next = NULL; + newelm->status = statuscode; + + if(last) { + last->next = newelm; + } else { + response->errors = newelm; + } + errlist = newelm; + } + + // we have the list -> add the new element + if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) { + return 1; + } + return 0; +} + +int msresponse_close(WebdavResource *res) { + MSResponse *response = (MSResponse*)res; + if(response->closing) { + return 0; // close already in progress + } + response->closing = TRUE; + Multistatus *ms = response->multistatus; + + int ret = REQ_PROCEED; + + // PROPFIND: + // response_close will execute propfind_do of all remaining backends + // after that we will have all available properties + WebdavOperation *op = ms->response.op; + if(op->response_close(op, res)) { + ret = REQ_ABORTED; + } + + // add missing properties with status code 404 + UcxAllocator *a = session_get_allocator(ms->sn); + WebdavPList *pl = ms->response.op->reqprops; + while(pl) { + sstr_t key = sstrcat_a( + a, + 3, + sstr((char*)pl->property->namespace->href), + S("\0"), + sstr((char*)pl->property->name)); + if(!ucx_map_sstr_get(response->properties, key)) { + // property was not added to this response + if(ms->proppatch) { + if(msresponse_addproperror(response, pl->property, 424)) { + ret = REQ_ABORTED; + break; + } + } else { + if(msresponse_addproperror(response, pl->property, 404)) { + ret = REQ_ABORTED; + break; + } + } + } + + pl = pl->next; + } + + if(ms->proppatch && response->errors) { + // a proppatch response must succeed entirely + // if we have a single error prop, move all props with status 200 + // to the error list + PropertyOkList *elm = response->plist_begin; + PropertyOkList *nextelm; + while(elm) { + if(msresponse_addproperror(response, elm->property, 424)) { + return 1; + } + nextelm = elm->next; + pool_free(response->multistatus->sn->pool, elm); + elm = nextelm; + } + response->plist_begin = NULL; + response->plist_end = NULL; + } + + // we don't need the properties anymore + ucx_map_free(response->properties); + + response->resource.isclosed = TRUE; + return ret; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/multistatus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/multistatus.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,157 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef MULTISTATUS_H +#define MULTISTATUS_H + +#include "../public/webdav.h" + +#include +#include +#include +#include "../util/writer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Multistatus Multistatus; +typedef struct MSResponse MSResponse; + +typedef struct PropertyOkList PropertyOkList; +typedef struct PropertyErrorList PropertyErrorList; + +/* + * implements the WebdavResponse interface + */ +struct Multistatus { + WebdavResponse response; + Session *sn; + Request *rq; + MSResponse *first; + MSResponse *current; + + /* + * Map of document namespace definitions + * + * key: (char*) namespace prefix + * value: WSNamespace* + */ + UcxMap *namespaces; + + /* + * Is this a proppatch request? + * + * In a proppatch response, when the first property with an error occurs, + * all already added properties will be set to 424 Failed Dependency. + */ + WSBool proppatch; +}; + +/* + * implements the WebdavResource interface + */ +struct MSResponse { + WebdavResource resource; + Multistatus *multistatus; + + /* + * Contains all properties that were added to the response + * key: null-byte + * value: WebdavProperty* + */ + UcxMap *properties; + + /* + * All properties with status != 200 + */ + PropertyErrorList *errors; + + /* + * All properties with status == 200 + */ + PropertyOkList *plist_begin; + PropertyOkList *plist_end; + + MSResponse *next; + WSBool closing; +}; + +struct PropertyOkList { + WebdavProperty *property; + WebdavNSList *nsdef; + PropertyOkList *next; +}; + +struct PropertyErrorList { + /* + * next list for different status code + */ + PropertyErrorList *next; + + /* + * property list for all properties with this status code + */ + WebdavPList *begin; + + /* + * tail of the property list + */ + WebdavPList *end; + + /* + * property response status code + */ + int status; +}; + +Multistatus* multistatus_response(Session *sn, Request *rq); + +int multistatus_send(Multistatus *ms, SYS_NETFD out); + +WebdavResource * multistatus_addresource( + WebdavResponse *response, + const char *path); + +int msresponse_addproperty( + WebdavResource *res, + WebdavProperty *property, + int status); + +int msresponse_addproperror( + MSResponse *response, + WebdavProperty *property, + int statuscode); + +int msresponse_close(WebdavResource *res); + +#ifdef __cplusplus +} +#endif + +#endif /* MULTISTATUS_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/objs.mk --- a/src/server/webdav/objs.mk Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/webdav/objs.mk Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. # -# Copyright 2013 Olaf Wintermann. All rights reserved. +# Copyright 2019 Olaf Wintermann. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -31,6 +31,12 @@ DAV_OBJPRE = $(OBJ_DIR)$(DAV_SRC_DIR) DAVOBJ = webdav.o +DAVOBJ += xml.o +DAVOBJ += requestparser.o +DAVOBJ += operation.o +DAVOBJ += multistatus.o +DAVOBJ += search.o +DAVOBJ += versioning.o DAVOBJS = $(DAVOBJ:%=$(DAV_OBJPRE)%) DAVSOURCE = $(DAVOBJ:%.o=webdav/%.c) diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/operation.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/operation.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,753 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include + +#include "../daemon/session.h" +#include "../util/pblock.h" + +#include "operation.h" + +#define WEBDAV_PATH_MAX 8192 + + +size_t webdav_num_backends(WebdavBackend *dav) { + size_t count = 0; + while(dav) { + count++; + dav = dav->next; + } + return count; +} + +/**************************************************************************** + * + * PROPFIND OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_propfind_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavPList *reqprops, + UcxList *requests, + WebdavResponse *response) +{ + WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); + ZERO(op, sizeof(WebdavOperation)); + op->dav = dav; + op->sn = sn; + op->rq = rq; + op->reqprops = reqprops; + op->requests = requests; + op->response = response; + op->response_close = webdav_op_propfiond_close_resource; + response->op = op; + + return op; +} + +int webdav_op_propfind_begin( + WebdavOperation *op, + const char *href, + VFS_DIR parent, + struct stat *s) +{ + // create WebdavResource object for requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + // store data that we need when the resource will be closed + op->stat = s; + op->parent = parent; + + // get first propfind object + WebdavPropfindRequest *propfind = op->requests->data; + + // execute propfind_do of the first backend for the first resource + int ret = REQ_PROCEED; + if(op->dav->propfind_do(propfind, op->response, NULL, resource, s)) { + ret = REQ_ABORTED; + } else { + // propfind_do successful, close resource if needed + // closing the resource will execute propfind_do of all remaining + // backends + if(!resource->isclosed) { + ret = resource->close(resource); + } + } + + return ret; +} + +typedef struct PathSearchElm { + char *href; + char *path; + size_t hreflen; + size_t pathlen; +} PathSearchElm; + +/* + * concats base + / + elm + * if baseinit is true, only elm is copied + */ +static int path_buf_concat( + pool_handle_t *pool, + char **buf, + size_t * restrict len, + WSBool * restrict baseinit, + const char *base, + size_t baselen, + const char *elm, + size_t elmlen) +{ + if(base[baselen-1] == '/') { + baselen--; + } + + size_t newlen = baselen + elmlen + 1; + if(newlen > WEBDAV_PATH_MAX) { + log_ereport(LOG_FAILURE, "webdav: maximal path length exceeded"); + return 1; + } + + // check if new path + terminator fits in the buffer + if(newlen + 1 > *len) { + *len = newlen + 128; + char *newbuf = pool_realloc(pool, *buf, newlen); + if(newbuf) { + log_ereport(LOG_FAILURE, "webdav: path memory allocation failed"); + return 1; + } + *baseinit = FALSE; + + *buf = newbuf; + } + + // if baseinit is true, the parent is already in the buffer + // and we don't need to memcpy it again + if(!(*baseinit)) { + memcpy(*buf, base, baselen); + (*buf)[baselen] = '/'; + *baseinit = TRUE; + } + // copy child and terminate string + memcpy((*buf) + baselen + 1, elm, elmlen); + (*buf)[newlen] = '\0'; + + return 0; +} + +static int propfind_child_cb( + VFSContext *vfs, + const char *href, + const char *path, + VFSDir *parent, + struct stat *s, + void *op) +{ + return webdav_op_propfind_begin(op, href, parent, s); +} + +int webdav_op_propfind_children( + WebdavOperation *op, + VFSContext *vfs, + const char *href, + const char *path) +{ + WebdavPropfindRequest *request = op->requests->data; + return webdav_op_iterate_children( + vfs, request->depth, href, path, propfind_child_cb, op); +} + +int webdav_op_propfiond_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + // start with second backend and request, because + // the first one was already called by webdav_op_propfind_begin + WebdavBackend *dav = op->dav->next; + UcxList *request = op->requests->next; + + // call propfind_do of all remaining backends + int ret = REQ_PROCEED; + while(dav && request) { + if(dav->propfind_do( + request->data, + op->response, + op->parent, + resource, + op->stat)) + { + ret = REQ_ABORTED; + } + + dav = dav->next; + request = request->next; + } + return ret; +} + +/* + * Executes propfind_finish for each Backend + */ +int webdav_op_propfind_finish(WebdavOperation *op) { + WebdavBackend *dav = op->dav; + UcxList *requests = op->requests; + + int ret = REQ_PROCEED; + while(dav && requests) { + if(dav->propfind_finish(requests->data)) { + ret = REQ_ABORTED; + } + + dav = dav->next; + requests = requests->next; + } + return ret; +} + +/**************************************************************************** + * + * PROPPATCH OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response) +{ + WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); + ZERO(op, sizeof(WebdavOperation)); + op->dav = dav; + op->sn = sn; + op->rq = rq; + op->reqprops = NULL; + op->response = response; + op->proppatch = proppatch; + op->response_close = webdav_op_proppatch_close_resource; + response->op = op; + + return op; +} + + + +int webdav_op_proppatch( + WebdavOperation *op, + const char *href, + const char *path) +{ + WebdavProppatchRequest *orig_request = op->proppatch; + UcxAllocator *a = session_get_allocator(op->sn); + + // create WebdavResource object for the requested resource + WebdavResource *resource = op->response->addresource(op->response, href); + if(!resource) { + return REQ_ABORTED; + } + + VFSContext *ctx = NULL; + VFSFile *file = NULL; + + // requests for each backends + WebdavProppatchRequest **requests = pool_calloc( + op->sn->pool, + webdav_num_backends(op->dav), + sizeof(WebdavProppatchRequest*)); + if(requests == NULL) { + return REQ_ABORTED; + } + + WebdavPList *prev_set = orig_request->set; + WebdavPList *prev_remove = orig_request->remove; + size_t set_count = orig_request->setcount; + size_t remove_count = orig_request->removecount; + + int ret = REQ_PROCEED; + + // iterate backends and execute proppatch_do + WebdavBackend *dav = op->dav; + size_t numrequests = 0; + while(dav) { + WebdavPList *set = webdav_plist_clone_s( + op->sn->pool, + prev_set, + &set_count); + WebdavPList *remove = webdav_plist_clone_s( + op->sn->pool, + prev_remove, + &remove_count); + if((prev_set && !set) || (prev_remove && !remove)) { + // clone failed, OOM + ret = REQ_ABORTED; + break; + } + + // create new WebdavProppatchRequest object for this backend + WebdavProppatchRequest *req = pool_malloc( + op->sn->pool, + sizeof(WebdavProppatchRequest)); + memcpy(req, orig_request, sizeof(WebdavProppatchRequest)); + req->dav = dav; + req->set = orig_request->set; + req->setcount = orig_request->setcount; + req->remove = orig_request->remove; + req->removecount = orig_request->removecount; + req->userdata = NULL; + + // check if we need to open the file because the backend wants it + if(!file && (dav->settings & WS_WEBDAV_PROPPATCH_USE_VFS) + == WS_WEBDAV_PROPPATCH_USE_VFS) + { + ctx = vfs_request_context(op->sn, op->rq); + if(!ctx) { + ret = REQ_ABORTED; + break; + } + + file = vfs_open(ctx, path, O_RDONLY); + if(!file) { + protocol_status( + op->sn, + op->rq, + util_errno2status(ctx->vfs_errno), + NULL); + ret = REQ_ABORTED; + } + } + + // execute proppatch_do + if(dav->proppatch_do(req, resource, file, &set, &remove)) { + // return later, because we need do execute proppatch_finish + // for all successfully called backends + ret = REQ_ABORTED; + break; + } + + // proppatch_do should remove all handled props from set and remove + // in the next iteration, the backend must use these reduced lists + prev_set = set; + prev_remove = remove; + + requests[numrequests++] = req; + + // continue with next backend + dav = dav->next; + } + + WSBool commit = FALSE; + if(ret == REQ_PROCEED && resource->err == 0) { + // no errors, no properties with errors -> save the changes + commit = TRUE; + } + + // call proppatch_finish for each successfully called proppatch_do + dav = op->dav; + int i = 0; + while(dav && i < numrequests) { + if(dav->proppatch_finish(requests[i], resource, file, commit)) { + ret = REQ_ABORTED; + } + i++; + dav = dav->next; + } + + if(file) { + vfs_close(file); + } + + if(resource->close(resource)) { + ret = REQ_ABORTED; + } + + return ret; +} + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + return 0; // NOP +} + + +/**************************************************************************** + * + * VFS OPERATION + * + ****************************************************************************/ + +WebdavVFSOperation* webdav_vfs_op( + Session *sn, + Request *rq, + WebdavBackend *dav, + WSBool precondition) +{ + WebdavVFSOperation *op = pool_malloc(sn->pool, sizeof(WebdavVFSOperation)); + if(!op) { + return NULL; + } + ZERO(op, sizeof(WebdavVFSOperation)); + + op->sn = sn; + op->rq = rq; + op->dav = dav; + op->stat = NULL; + op->stat_errno = 0; + + // create VFS context + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + pool_free(sn->pool, op); + return NULL; + } + op->vfs = vfs; + + char *path = pblock_findkeyval(pb_key_path, rq->vars); + op->path = path; + + return op; +} + +WebdavVFSOperation webdav_vfs_sub_op( + WebdavVFSOperation *op, + char *path, + struct stat *s) +{ + WebdavVFSOperation sub; + sub.dav = op->dav; + sub.path = path; + sub.sn = op->sn; + sub.vfs = op->vfs; + sub.path = path; + sub.stat = s; + sub.stat_errno = 0; + return sub; +} + +int webdav_op_iterate_children( + VFSContext *vfs, + int depth, + const char *href, + const char *path, + vfs_op_child_func func, + void *userdata) +{ + UcxAllocator *a = session_get_allocator(vfs->sn); + pool_handle_t *pool = vfs->sn->pool; + + PathSearchElm *start_elm = pool_malloc(pool, sizeof(PathSearchElm)); + start_elm->href = pool_strdup(pool, href ? href : ""); + start_elm->path = pool_strdup(pool, path ? path : ""); + start_elm->hreflen = href ? strlen(href) : 0; + start_elm->pathlen = path ? strlen(path) : 0; + + UcxList *stack = ucx_list_prepend_a(a, NULL, start_elm); + UcxList *stack_end = stack; + if(!stack) { + return 1; + } + + // reusable buffer for full child path and href + char *newpath = pool_malloc(pool, 256); + size_t newpathlen = 256; + + char *newhref = pool_malloc(pool, 256); + size_t newhreflen = 256; + + int err = 0; + while(stack && !err) { + PathSearchElm *cur_elm = stack->data; + + // when newpath is initialized with the parent path + // set path_buf_init to TRUE + WSBool href_buf_init = FALSE; + WSBool path_buf_init = FALSE; + + VFS_DIR dir = vfs_opendir(vfs, cur_elm->path); + if(!dir) { + log_ereport( + LOG_FAILURE, + "webdav: propfind: cannot open directory %d", + vfs->vfs_errno); + err = 1; + break; + } + + VFS_ENTRY f; + while(vfs_readdir_stat(dir, &f)) { + if(f.stat_errno != 0) { + continue; + } + + size_t child_len = strlen(f.name); + + // create new path and href for the child + if(path_buf_concat( + pool, + &newhref, + &newhreflen, + &href_buf_init, + cur_elm->href, + cur_elm->hreflen, + f.name, + child_len)) + { + err = 1; + break; + } + if(path_buf_concat( + pool, + &newpath, + &newpathlen, + &path_buf_init, + cur_elm->path, + cur_elm->pathlen, + f.name, + child_len)) + { + err = 1; + break; + } + size_t childhreflen = cur_elm->hreflen + 1 + child_len; + size_t childpathlen = cur_elm->pathlen + 1 + child_len; + + // execute callback func for this file + if(func(vfs, newhref, newpath, dir, &f.stat, userdata)) { + err = 1; + break; + } + + // depth of -1 means infinity + if(depth == -1 && S_ISDIR(f.stat.st_mode)) { + char *hrefcp = pool_malloc(pool, childhreflen + 1); + memcpy(hrefcp, newhref, childhreflen + 1); + hrefcp[childhreflen] = '\0'; + + char *pathcp = pool_malloc(pool, childpathlen + 1); + memcpy(pathcp, newpath, childpathlen + 1); + pathcp[childpathlen] = '\0'; + + PathSearchElm *new_elm = pool_malloc(pool, + sizeof(PathSearchElm)); + new_elm->href = hrefcp; + new_elm->path = pathcp; + new_elm->hreflen = childhreflen; + new_elm->pathlen = childpathlen; + + // add the new_elm to the stack + // stack_end is always not NULL here, because we remove + // the first stack element at the end of the loop + UcxList *newlistelm = ucx_list_append_a(a, stack_end, new_elm); + if(!newlistelm) { + err = 1; + break; + } + stack_end = newlistelm; + } + } + + vfs_closedir(dir); + + pool_free(pool, cur_elm->path); + pool_free(pool, cur_elm->href); + pool_free(pool, cur_elm); + + stack = ucx_list_remove_a(a, stack, stack); + } + + // in case of an error, we have to free all remaining stack elements + UCX_FOREACH(elm, stack) { + char *data = elm->data; + if(data != path) { + pool_free(pool, data); + } + } + + return err; +} + + +int webdav_vfs_stat(WebdavVFSOperation *op) { + if(op->stat) { + return 0; + } else if(op->stat_errno != 0) { + // previous stat failed + return 1; + } + + // stat file + struct stat sbuf; + int ret = vfs_stat(op->vfs, op->path, &sbuf); + if(!ret) { + // save result in op->stat and in s + op->stat = pool_malloc(op->sn->pool, sizeof(struct stat)); + if(op->stat) { + memcpy(op->stat, &sbuf, sizeof(struct stat)); + } else { + ret = 1; + op->stat_errno = ENOMEM; + } + } else { + op->stat_errno = errno; + } + + return ret; +} + +int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type) { + WSBool exec_vfs = TRUE; + + // requests for each backends + WebdavVFSRequest **requests = pool_calloc( + op->sn->pool, + webdav_num_backends(op->dav), + sizeof(WebdavVFSRequest*)); + if(requests == NULL) { + return REQ_ABORTED; + } + + int ret = REQ_PROCEED; + + // call opt_* func for each backend + WebdavBackend *dav = op->dav; + int called_backends = 0; + while(dav) { + WebdavVFSRequest *request = NULL; + + // get vfs operation functions + vfs_op_func op_func = NULL; + vfs_op_finish_func op_finish_func = NULL; + + if(type == WEBDAV_VFS_MKDIR) { + op_func = dav->opt_mkcol; + op_finish_func = dav->opt_mkcol_finish; + } else if(type == WEBDAV_VFS_DELETE) { + op_func = dav->opt_delete; + op_finish_func = dav->opt_delete_finish; + } + + if(op_func || op_finish_func) { + // we need a request object + request = pool_malloc(op->sn->pool, sizeof(WebdavVFSRequest)); + if(!request) { + exec_vfs = FALSE; + ret = REQ_ABORTED; + break; + } + request->sn = op->sn; + request->rq = op->rq; + request->path = op->path; + request->userdata = NULL; + + requests[called_backends] = request; + } + + // exec backend func for this operation + // this will set 'done' to TRUE, if no further vfs call is required + WSBool done = FALSE; + called_backends++; + if(op_func) { + if(op_func(request, &done)) { + exec_vfs = FALSE; + ret = REQ_ABORTED; + break; + } + } + if(done) { + exec_vfs = FALSE; + } + + dav = dav->next; + } + + // if needed, call vfs func for this operation + if(exec_vfs) { + int r = 0; + if(type == WEBDAV_VFS_MKDIR) { + r = vfs_mkdir(op->vfs, op->path); + } else if(type == WEBDAV_VFS_DELETE) { + r = webdav_vfs_unlink(op); + } + + if(r) { + ret = REQ_ABORTED; + } + } + + WSBool success = ret == REQ_PROCEED ? TRUE : FALSE; + + // finish mkcol (cleanup) by calling opt_*_finish for each backend + dav = op->dav; + int i = 0; + while(dav && i < called_backends) { + // get vfs operation functions + vfs_op_finish_func op_finish_func = NULL; + + if(type == WEBDAV_VFS_MKDIR) { + op_finish_func = dav->opt_mkcol_finish; + } else if(type == WEBDAV_VFS_DELETE) { + op_finish_func = dav->opt_delete_finish; + } + + if(op_finish_func) { + if(op_finish_func(requests[i], success)) { + ret = REQ_ABORTED; // don't exit loop + } + } + + dav = dav->next; + i++; + } + + return ret; +} + +int webdav_vfs_unlink(WebdavVFSOperation *op) { + // stat the file first, to check if the file is a directory + if(webdav_vfs_stat(op)) { + return 1; // error + } else { + if(!S_ISDIR(op->stat->st_mode)) { + return vfs_unlink(op->vfs, op->path); + } else { + return vfs_rmdir(op->vfs, op->path); + } + } +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/operation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/operation.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,199 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OPERATION_H +#define OPERATION_H + +#include "../public/webdav.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int(*response_close_func)(WebdavOperation *, WebdavResource *); + +typedef struct WebdavVFSOperation WebdavVFSOperation; + +typedef struct WebdavCopy WebdavCopy; +typedef struct CopyResource CopyResource; + +struct WebdavOperation { + WebdavBackend *dav; + Request *rq; + Session *sn; + + WebdavProppatchRequest *proppatch; /* proppatch request or NULL */ + WebdavPList *reqprops; /* requested properties */ + UcxList *requests; /* backend specific request objects */ + + WebdavResponse *response; + + response_close_func response_close; + + VFS_DIR parent; /* current directory */ + struct stat *stat; /* current stat object */ +}; + +struct WebdavVFSOperation { + WebdavBackend *dav; + Request *rq; + Session *sn; + + VFSContext *vfs; + + char *path; + struct stat *stat; + int stat_errno; +}; + +struct WebdavCopy { + WebdavResponse response; + Session *sn; + Request *rq; + CopyResource *current; + + char *src_href; + char *src_path; + char *dst_href; + char *dst_path; +}; + +struct CopyResource { + WebdavResource resource; + UcxMap *properties; +}; + +enum WebdavVFSOpType { + WEBDAV_VFS_MKDIR = 0, + WEBDAV_VFS_DELETE +}; + +typedef enum WebdavVFSOpType WebdavVFSOpType; + +typedef int(*vfs_op_func)(WebdavVFSRequest *, WSBool *); +typedef int(*vfs_op_finish_func)(WebdavVFSRequest *, WSBool); + +typedef int(*vfs_op_child_func)( + VFSContext *, + const char *, /* href */ + const char *, /* path */ + VFSDir *, /* parent dir */ + struct stat *, /* child stat */ + void *); /* user data */ + +/* + * counts the number of backends + */ +size_t webdav_num_backends(WebdavBackend *dav); + +WebdavOperation* webdav_create_propfind_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavPList *reqprops, + UcxList *requests, + WebdavResponse *response); + +int webdav_op_propfind_begin( + WebdavOperation *op, + const char *href, + VFS_DIR parent, + struct stat *s); + +int webdav_op_propfind_children( + WebdavOperation *op, + VFSContext *vfs, + const char *href, + const char *path); + +int webdav_op_propfiond_close_resource( + WebdavOperation *op, + WebdavResource *resource); + +int webdav_op_propfind_finish(WebdavOperation *op); + +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response); + +int webdav_op_proppatch( + WebdavOperation *op, + const char *href, + const char *path); + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource); + + +WebdavVFSOperation* webdav_vfs_op( + Session *sn, + Request *rq, + WebdavBackend *dav, + WSBool precondition); + +WebdavVFSOperation webdav_vfs_sub_op( + WebdavVFSOperation *op, + char *path, + struct stat *s); + +int webdav_op_iterate_children( + VFSContext *vfs, + int depth, + const char *href, + const char *path, + vfs_op_child_func func, + void *userdata); + +int webdav_vfs_stat(WebdavVFSOperation *op); + +int webdav_vfs_op_do(WebdavVFSOperation *op, WebdavVFSOpType type); + +int webdav_vfs_unlink(WebdavVFSOperation *op); + + +WebdavCopy* webdav_copy_create( + Session *sn, + Request *rq, + VFSContext *vfs, + char *from_href, + char *from_path, + char *to_href, + char *to_path); + +#ifdef __cplusplus +} +#endif + +#endif /* OPERATION_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/requestparser.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/requestparser.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,496 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include + +#include "requestparser.h" +#include "webdav.h" + +#define xstreq(a, b) !strcmp((const char*)a, (const char*)b) + +void proplist_free(pool_handle_t *pool, WebdavPList *list) { + while(list) { + WebdavPList *next = list->next; + pool_free(pool, list); + list = next; + } +} + +WebdavProperty* prop_create( + pool_handle_t *pool, + WSNamespace *ns, + const char *name) +{ + WebdavProperty *prop = pool_malloc(pool, sizeof(WebdavProperty)); + memset(prop, 0, sizeof(WebdavProperty)); + prop->lang = NULL; + prop->name = (char*)name; + prop->namespace = ns; + return prop; +} + +static int parse_prop( + Session *sn, + xmlNode *node, + UcxMap *propmap, + WebdavPList **plist_begin, + WebdavPList **plist_end, + size_t *propcount, + int proppatch, + int *error) +{ + xmlNode *pnode = node->children; + for(;pnode;pnode=pnode->next) { + if(pnode->type != XML_ELEMENT_NODE) { + continue; + } + + const char* ns = (const char*)pnode->ns->href; + const char* name = (const char*)pnode->name; + + // check for prop duplicates + UcxKey k = webdav_property_key((const char*)ns, (const char*)name); + if(!k.data) { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + return 1; + } + void *c = ucx_map_get(propmap, k); + if(!c) { + if(ucx_map_put(propmap, k, (void*)1)) { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + + // no duplicate + // create property elment and add it to the list + WebdavProperty *prop = prop_create(sn->pool, pnode->ns, name); + if(proppatch) { + prop->value.node = pnode->children; + prop->vtype = WS_VALUE_XML_NODE; + } + if(prop) { + if(webdav_plist_add(sn->pool, plist_begin, plist_end, prop)) { + *error = proppatch ? + PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + (*propcount)++; + } else { + *error = proppatch ? PROPPATCH_PARSER_OOM : PROPFIND_PARSER_OOM; + } + } else if(proppatch) { + *error = PROPPATCH_PARSER_DUPLICATE; + } + + free((void*)k.data); + if(*error) { + return 1; + } + } + return 0; +} + +WebdavPropfindRequest* propfind_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = PROPFIND_PARSER_INVALID_REQUEST; + return NULL; + } + + // ret vars + *error = 0; + + WSBool allprop = FALSE; + WSBool propname = FALSE; + WebdavPList *plist_begin = NULL; + WebdavPList *plist_end = NULL; + size_t propcount = 0; + int depth = webdav_getdepth(rq); + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is DAV:propfind + if( + !(root->ns + && xstreq(root->ns->href, "DAV:") + && xstreq(root->name, "propfind"))) + { + *error = PROPFIND_PARSER_NO_PROPFIND; + xmlFreeDoc(doc); + return NULL; + } + + UcxMap *propmap = ucx_map_new(32); + if(!propmap) { + *error = PROPFIND_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->ns->href, "DAV:") && !allprop && !propname) { + // a propfind request can contain a prop element + // with specified properties or the allprop or propname + // element + if(xstreq(node->name, "prop")) { + ret = parse_prop( + sn, + node, + propmap, + &plist_begin, + &plist_end, + &propcount, + 0, // proppatch = false + error); + } else if(xstreq(node->name, "allprop")) { + allprop = TRUE; + } else if(xstreq(node->name, "propname")) { + propname = TRUE; + } + } + } + node = node->next; + } + + ucx_map_free(propmap); // no allocated content must be freed + + if(ret) { + // parse_prop failed + // in this case, error is already set + xmlFreeDoc(doc); + return NULL; + } + + if(!allprop && !propname && propcount == 0) { + *error = PROPFIND_PARSER_NO_PROPERTIES; + xmlFreeDoc(doc); + return NULL; + } + + WebdavPropfindRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavPropfindRequest)); + if(!request) { + *error = PROPFIND_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->properties = NULL; + request->propcount = 0; + request->depth = depth; + request->doc = doc; + if(allprop) { + request->allprop = TRUE; + request->propname = FALSE; // we cannot have allprop and propname + } else if(propname) { + request->allprop = FALSE; + request->propname = TRUE; + } else { + request->allprop = FALSE; + request->propname = FALSE; + request->properties = plist_begin; + request->propcount = propcount; + } + + if(!request->properties && plist_begin) { + proplist_free(sn->pool, plist_begin); + } + return request; +} + +WebdavProppatchRequest* proppatch_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + return webdav_parse_set( + sn, rq, buf, buflen, "DAV:", "propertyupdate", TRUE, error); +} + +static xmlNode* find_child(xmlNode *node, const char *name) { + xmlNode *c = node->children; + while(c) { + if(c->ns) { + if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, name)) { + return c; + } + } + c = c->next; + } + return NULL; +} + +WebdavProppatchRequest* webdav_parse_set( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + const char *rootns, + const char *rootname, + WSBool allowremove, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = PROPPATCH_PARSER_INVALID_REQUEST; + return NULL; + } + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is correct + if( + !(root->ns + && xstreq(root->ns->href, rootns) + && xstreq(root->name, rootname))) + { + *error = PROPPATCH_PARSER_NO_PROPERTYUPDATE; + xmlFreeDoc(doc); + return NULL; + } + + // ret vars + *error = 0; + + UcxMap *propmap = ucx_map_new(32); // map for duplicate checking + if(!propmap) { + *error = PROPPATCH_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + + WebdavPList *set_begin = NULL; + WebdavPList *set_end = NULL; + WebdavPList *remove_begin = NULL; + WebdavPList *remove_end = NULL; + size_t set_count = 0; + size_t remove_count = 0; + + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(node->ns && xstreq(node->ns->href, "DAV:")) { + // a propfind request can contain a prop element + // with specified properties or the allprop or propname + // element + if(xstreq(node->name, "set")) { + xmlNode *prop = find_child(node, "prop"); + ret = parse_prop( + sn, + prop, + propmap, + &set_begin, + &set_end, + &set_count, + TRUE, // proppatch = true + error); + } else if(xstreq(node->name, "remove")) { + if(!allowremove) { + *error = PROPPATCH_PARSER_INVALID_REQUEST; + ret = 1; + break; + } + xmlNode *prop = find_child(node, "prop"); + ret = parse_prop( + sn, + prop, + propmap, + &remove_begin, + &remove_end, + &remove_count, + TRUE, // proppatch = true + error); + } + + } + } + node = node->next; + } + + ucx_map_free(propmap); // allocated content must not be freed + + if(set_count + remove_count == 0) { + *error = PROPPATCH_PARSER_NO_PROPERTIES; + ret = 1; + } + + if(ret) { + xmlFreeDoc(doc); + return NULL; + } + + WebdavProppatchRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavProppatchRequest)); + if(!request) { + *error = PROPPATCH_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->doc = doc; + request->set = set_begin; + request->setcount = set_count; + request->remove = remove_begin; + request->removecount = remove_count; + return request; +} + +WebdavLockRequest* lock_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error) +{ + xmlDoc *doc = xmlReadMemory(buf, buflen, NULL, NULL, 0); + if(!doc) { + *error = LOCK_PARSER_INVALID_REQUEST; + return NULL; + } + + xmlNode *root = xmlDocGetRootElement(doc); + xmlNode *node = root->children; + + // check if the root element is correct + if( + !(root->ns + && xstreq(root->ns->href, "DAV:") + && xstreq(root->name, "lockinfo"))) + { + *error = LOCK_PARSER_NO_LOCKINFO; + xmlFreeDoc(doc); + return NULL; + } + + WebdavLockScope lockscope = WEBDAV_LOCK_SCOPE_UNKNOWN; + WebdavLockType locktype = WEBDAV_LOCK_TYPE_UNKNOWN; + WSXmlNode *owner = NULL; + + int ret = 0; + while(node && !ret) { + if( + node->type == XML_ELEMENT_NODE + && node->ns + && xstreq(node->ns->href, "DAV:")) + { + char *name = (char*)node->name; + if(xstreq(name, "lockscope")) { + xmlNode *s = node->children; + while(s) { + if( + s->type == XML_ELEMENT_NODE + && s->ns + && xstreq(s->ns->href, "DAV:")) + { + if(xstreq(s->name, "exclusive")) { + lockscope = WEBDAV_LOCK_EXCLUSIVE; + } else if(xstreq(s->name, "shared")) { + lockscope = WEBDAV_LOCK_SHARED; + } else { + // don't ignore unknown lockscope + *error = LOCK_PARSER_UNKNOWN_ELEMENT; + ret = 1; + break; + } + } + s = s->next; + } + } else if(xstreq(name, "locktype")) { + xmlNode *t = node->children; + while(t) { + if( + t->type == XML_ELEMENT_NODE + && t->ns + && xstreq(t->ns->href, "DAV:")) + { + if(xstreq(t->name, "write")) { + locktype = WEBDAV_LOCK_WRITE; + } else { + *error = LOCK_PARSER_UNKNOWN_ELEMENT; + ret = 1; + break; + } + } + t = t->next; + } + } else if(xstreq(name, "owner")) { + owner = node->children; + } + } + node = node->next; + } + + if(ret) { + xmlFreeDoc(doc); + return NULL; + } + + WebdavLockRequest *request = pool_malloc( + sn->pool, + sizeof(WebdavLockRequest)); + if(!request) { + *error = LOCK_PARSER_OOM; + xmlFreeDoc(doc); + return NULL; + } + request->sn = sn; + request->rq = rq; + request->doc = doc; + + if(locktype == WEBDAV_LOCK_TYPE_UNKNOWN) { + locktype = WEBDAV_LOCK_WRITE; + } + if(lockscope == WEBDAV_LOCK_SCOPE_UNKNOWN) { + lockscope = WEBDAV_LOCK_EXCLUSIVE; + } + request->scope = lockscope; + request->type = locktype; + request->owner = owner; + return request; +} + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/requestparser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/requestparser.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,106 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef REQUESTPARSER_H +#define REQUESTPARSER_H + +#include "../public/webdav.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROPFIND_PARSER_OK 0 +#define PROPFIND_PARSER_NO_PROPFIND 1 +#define PROPFIND_PARSER_NO_PROPERTIES 2 +#define PROPFIND_PARSER_INVALID_REQUEST 3 +#define PROPFIND_PARSER_OOM 4 +#define PROPFIND_PARSER_ERROR 5 + +#define PROPPATCH_PARSER_OK 0 +#define PROPPATCH_PARSER_NO_PROPERTYUPDATE 1 +#define PROPPATCH_PARSER_NO_PROPERTIES 2 +#define PROPPATCH_PARSER_INVALID_REQUEST 3 +#define PROPPATCH_PARSER_DUPLICATE 4 +#define PROPPATCH_PARSER_OOM 5 +#define PROPPATCH_PARSER_ERROR 6 + +#define LOCK_PARSER_OK 0 +#define LOCK_PARSER_NO_LOCKINFO 1 +#define LOCK_PARSER_INVALID_REQUEST 2 +#define LOCK_PARSER_UNKNOWN_ELEMENT 3 +#define LOCK_PARSER_OOM 4 +#define LOCK_PARSER_ERROR 5 + + +WebdavProperty* prop_create( + pool_handle_t *pool, + WSNamespace *ns, + const char *name); + +WebdavPropfindRequest* propfind_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + +WebdavProppatchRequest* proppatch_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + +WebdavProppatchRequest* webdav_parse_set( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + const char *rootns, + const char *rootname, + WSBool allowremove, + int *error); + +WebdavLockRequest* lock_parse( + Session *sn, + Request *rq, + const char *buf, + size_t buflen, + int *error); + + +#ifdef __cplusplus +} +#endif + +#endif /* REQUESTPARSER_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/search.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/search.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,33 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "search.h" + +int webdav_search (pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/search.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/search.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SEARCH_H +#define SEARCH_H + +#include "../public/webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int webdav_search (pblock *pb, Session *sn, Request *rq); + + +#ifdef __cplusplus +} +#endif + +#endif /* SEARCH_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/versioning.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/versioning.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "versioning.h" + +int webdav_version_control(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_checkout(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_checkin(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_uncheckout(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_update(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_label(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_merge(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/versioning.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/versioning.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef VERSIONING_H +#define VERSIONING_H + +#include "../public/webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int webdav_version_control(pblock *pb, Session *sn, Request *rq); +int webdav_checkout(pblock *pb, Session *sn, Request *rq); +int webdav_checkin(pblock *pb, Session *sn, Request *rq); +int webdav_uncheckout(pblock *pb, Session *sn, Request *rq); +int webdav_mkworkspace(pblock *pb, Session *sn, Request *rq); +int webdav_update(pblock *pb, Session *sn, Request *rq); +int webdav_label(pblock *pb, Session *sn, Request *rq); +int webdav_merge(pblock *pb, Session *sn, Request *rq); + + +#ifdef __cplusplus +} +#endif + +#endif /* VERSIONING_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/webdav.c --- a/src/server/webdav/webdav.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/webdav/webdav.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,6 +30,1268 @@ #include #include +#include +#include + #include "webdav.h" +#include "search.h" +#include "versioning.h" +#include "multistatus.h" +#include "requestparser.h" +#include "operation.h" +#include "../util/pblock.h" +#include "../util/util.h" +#include "../daemon/session.h" +#include "../daemon/http.h" +#include "../daemon/protocol.h" +#include "../daemon/vfs.h" + +/* + * http method fptr mapping + * key: http method name (string) + * value: SAF fptr + */ +static UcxMap *method_handler_map; + +/* + * webdav backend types + * key: backend name (string) + * value: WebdavBackend* + */ +static UcxMap *webdav_type_map; + +static WebdavBackend default_backend; + +static WSNamespace dav_namespace; + +static WebdavProperty dav_resourcetype_empty; +static WebdavProperty dav_resourcetype_collection; +static WSXmlData dav_resourcetype_collection_value; + +#define WEBDAV_RESOURCE_TYPE_COLLECTION "" + +static void init_default_backend(void) { + memset(&default_backend, 0, sizeof(WebdavBackend)); + default_backend.propfind_init = default_propfind_init; + default_backend.propfind_do = default_propfind_do; + default_backend.propfind_finish = default_propfind_finish; + default_backend.proppatch_do = default_proppatch_do; + default_backend.proppatch_finish = default_proppatch_finish; + default_backend.opt_mkcol = NULL; + default_backend.opt_delete = NULL; + default_backend.settings = WS_WEBDAV_PROPFIND_USE_VFS; + default_backend.instance = NULL; +} + +int webdav_register_backend(const char *name, webdav_init_func webdavInit, webdav_create_func webdavCreate) { + WebdavType *webdavType = malloc(sizeof(WebdavType)); + webdavType->init = webdavInit; + webdavType->create = webdavCreate; + return ucx_map_cstr_put(webdav_type_map, name, webdavType); +} + +WebdavType* webdav_get_type(scstr_t dav_class) { + return ucx_map_sstr_get(webdav_type_map, dav_class); +} + +void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error) { + *error = 0; + if(dav_class->init) { + void *initData = dav_class->init(cfg, pool, config); + if(!initData) { + *error = 1; + } + return initData; + } else { + return NULL; + } +} + +WebdavBackend* webdav_create(Session *sn, Request *rq, const char *dav_class, pblock *pb, void *initData) { + WebdavType *webdavType = ucx_map_cstr_get(webdav_type_map, dav_class); + if(!webdavType) { + log_ereport(LOG_MISCONFIG, "webdav_create: unkown dav type %s", dav_class); + return NULL; + } + + return webdavType->create(sn, rq, pb, initData); +} + +static WSBool webdav_is_initialized = FALSE; + +int webdav_init(pblock *pb, Session *sn, Request *rq) { + if(webdav_is_initialized) { + return REQ_NOACTION; + } + webdav_is_initialized = TRUE; + + webdav_type_map = ucx_map_new(8); + if(!webdav_type_map) { + return REQ_ABORTED; + } + + method_handler_map = ucx_map_new(64); + if(!method_handler_map) { + return REQ_ABORTED; + } + + init_default_backend(); + ucx_map_cstr_put(webdav_type_map, "default", &default_backend); + + ucx_map_cstr_put(method_handler_map, "OPTIONS", webdav_options); + ucx_map_cstr_put(method_handler_map, "PROPFIND", webdav_propfind); + ucx_map_cstr_put(method_handler_map, "PROPPATCH", webdav_proppatch); + ucx_map_cstr_put(method_handler_map, "MKCOL", webdav_mkcol); + ucx_map_cstr_put(method_handler_map, "POST", webdav_post); + ucx_map_cstr_put(method_handler_map, "DELETE", webdav_delete); + ucx_map_cstr_put(method_handler_map, "PUT", webdav_put); + ucx_map_cstr_put(method_handler_map, "COPY", webdav_copy); + ucx_map_cstr_put(method_handler_map, "MOVE", webdav_move); + ucx_map_cstr_put(method_handler_map, "LOCK", webdav_lock); + ucx_map_cstr_put(method_handler_map, "UNLOCK", webdav_unlock); + ucx_map_cstr_put(method_handler_map, "REPORT", webdav_report); + ucx_map_cstr_put(method_handler_map, "ACL", webdav_acl); + + ucx_map_cstr_put(method_handler_map, "SEARCH", webdav_search); + + ucx_map_cstr_put(method_handler_map, "VERSION-CONTROL", webdav_version_control); + ucx_map_cstr_put(method_handler_map, "CHECKOUT", webdav_checkout); + ucx_map_cstr_put(method_handler_map, "CHECKIN", webdav_checkin); + ucx_map_cstr_put(method_handler_map, "UNCHECKOUT", webdav_uncheckout); + ucx_map_cstr_put(method_handler_map, "MKWORKSPACE", webdav_mkworkspace); + ucx_map_cstr_put(method_handler_map, "UPDATE", webdav_update); + ucx_map_cstr_put(method_handler_map, "LABEL", webdav_label); + ucx_map_cstr_put(method_handler_map, "MERGE", webdav_merge); + + dav_namespace.href = (xmlChar*)"DAV:"; + dav_namespace.prefix = (xmlChar*)"D"; + + dav_resourcetype_empty.namespace = &dav_namespace; + dav_resourcetype_empty.name = "resourcetype"; + + dav_resourcetype_collection_value.data = WEBDAV_RESOURCE_TYPE_COLLECTION; + dav_resourcetype_collection_value.length = sizeof(WEBDAV_RESOURCE_TYPE_COLLECTION)-1; + + dav_resourcetype_collection.namespace = &dav_namespace; + dav_resourcetype_collection.name = "resourcetype"; + dav_resourcetype_collection.value.data = dav_resourcetype_collection_value; + dav_resourcetype_collection.vtype = WS_VALUE_XML_DATA; + + return REQ_PROCEED; +} + + +int webdav_service(pblock *pb, Session *sn, Request *rq) { + if(!method_handler_map) { + log_ereport(LOG_FAILURE, "WebDAV module not initialized"); + protocol_status(sn, rq, 500, NULL); + return REQ_ABORTED; + } + char *method = pblock_findkeyval(pb_key_method, rq->reqpb); + + FuncPtr saf = (FuncPtr)ucx_map_cstr_get(method_handler_map, method); + if(!saf) { + return REQ_NOACTION; + } + + return saf(pb, sn, rq); +} + +UcxBuffer* rqbody2buffer(Session *sn, Request *rq) { + if(!sn->inbuf) { + //request body required, set http response code + protocol_status(sn, rq, 400, NULL); + return NULL; + } + + UcxBuffer *buf = ucx_buffer_new( + NULL, + sn->inbuf->maxsize, + UCX_BUFFER_AUTOEXTEND); + if(!buf) { + protocol_status(sn, rq, 500, NULL); + return NULL; + } + + char in[2048]; + int r; + while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { + if(ucx_buffer_write(in, 1, r, buf) != r) { + protocol_status(sn, rq, 500, NULL); + ucx_buffer_free(buf); + return NULL; + } + } + + return buf; +} + +int webdav_options(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_propfind(pblock *pb, Session *sn, Request *rq) { + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + UcxBuffer *reqbody = rqbody2buffer(sn, rq); + if(!reqbody) { + return REQ_ABORTED; + } + + UcxAllocator *a = session_get_allocator(sn); + + int error = 0; + WebdavPropfindRequest *propfind = propfind_parse( + sn, + rq, + reqbody->space, + reqbody->size, + &error); + ucx_buffer_free(reqbody); + if(!propfind) { + switch(error) { + // TODO: handle all errors + default: return REQ_ABORTED; + } + } + + WebdavBackend *dav = rq->davCollection ? + rq->davCollection : &default_backend; + + + // requested uri and path + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + // The multistatus response object contains responses for all + // requested resources. At the end the Multistatus object will be + // serialized to xml + Multistatus *ms = multistatus_response(sn, rq); + if(!ms) { + return REQ_ABORTED; + } + + + int ret = webdav_propfind_do(dav, propfind, (WebdavResponse*)ms, NULL, path, uri); + + // if propfind was successful, send the result to the client + if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) { + ret = REQ_ABORTED; + // TODO: log error + } else { + // TODO: error response + } + + // cleanup + if(propfind->doc) { + xmlFreeDoc(propfind->doc); + } + + return ret; +} + +/* + * Initializes Backend Chain + * + * Calls propfind_init of each Backend and generates a list of custom + * WebdavPropfindRequest objects for each backend + */ +int webdav_propfind_init( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + const char *path, + const char *uri, + UcxList **out_req) +{ + pool_handle_t *pool = propfind->sn->pool; + UcxAllocator *a = session_get_allocator(propfind->sn); + + // list of individual WebdavPropfindRequest objects for each Backend + UcxList *requestObjects = NULL; + + // new properties after init, start with clone of original plist + WebdavPList *newProp = webdav_plist_clone(pool, propfind->properties); + size_t newPropCount = propfind->propcount; + + // Call propfind_init for each Backend + // propfind_init can return a new property list, which + // will be passed to the next backend + + WebdavBackend *davList = dav; + while(davList) { + // create WebdavPropfindRequest copy + WebdavPropfindRequest *pReq = pool_malloc( + pool, + sizeof(WebdavPropfindRequest)); + memcpy(pReq, propfind, sizeof(WebdavPropfindRequest)); + // use new plist after previous init (or orig. plist in the first run) + pReq->properties = newProp; + pReq->propcount = newPropCount; + pReq->dav = davList; + + // add new WebdavPropfindRequest object to list for later use + requestObjects = ucx_list_append_a(a, requestObjects, pReq); + if(!requestObjects) { + return REQ_ABORTED; // OOM + } + + // create plist copy as out-plist for init + newProp = webdav_plist_clone(pool, newProp); + + // run init: this can generate a new properties list (newProp) + // which will be passed to the next backend + if(davList->propfind_init(pReq, path, uri, &newProp)) { + return REQ_ABORTED; + } + + newPropCount = webdav_plist_size(newProp); + + davList = davList->next; + } + + *out_req = requestObjects; + return REQ_PROCEED; +} + +int webdav_propfind_do( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFSContext *vfs, + char *path, + char *uri) +{ + Session *sn = propfind->sn; + Request *rq = propfind->rq; + + // VFS settings are only taken from the first backend + uint32_t settings = dav->settings; + + // list of individual WebdavPropfindRequest objects for each Backend + UcxList *requestObjects = NULL; + + // Initialize all Webdav Backends + if(webdav_propfind_init(dav, propfind, path, uri, &requestObjects)) { + return REQ_ABORTED; + } + + WebdavOperation *op = webdav_create_propfind_operation( + sn, + rq, + dav, + propfind->properties, + requestObjects, + response); + + // some Backends can list all children by themselves, but some + // require the VFS for this + WSBool usevfs = (settings & WS_WEBDAV_PROPFIND_USE_VFS) + == WS_WEBDAV_PROPFIND_USE_VFS; + struct stat s; + struct stat *statptr = NULL; + + if(usevfs && !vfs) { + vfs = vfs_request_context(sn, rq); + if(!vfs) { + return REQ_ABORTED; + } + + if(vfs_stat(vfs, path, &s)) { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + statptr = &s; + if(!S_ISDIR(s.st_mode)) { + // the file is not a directory, therefore we don't need the VFS + usevfs = FALSE; + } + } + if(propfind->depth == 0) { + usevfs = FALSE; + } + + int ret = REQ_PROCEED; + + // create WebdavResource object for requested resource + if(!webdav_op_propfind_begin(op, uri, NULL, statptr)) { + // propfind for the requested resource was successful + + // usevfsdir is TRUE if + // the webdav backend has not disabled vfs usage + // the file is a directory + // depth is not 0 + // in this case we need to execute propfind_do for all children + if(usevfs) { + if(webdav_op_propfind_children(op, vfs, uri, path)) { + ret = REQ_ABORTED; + } + } + } + + // finish the propfind request + // this function should cleanup all resources, therefore we execute it + // even if a previous function failed + if(webdav_op_propfind_finish(op)) { + // TODO: log error + ret = REQ_ABORTED; + } + + return ret; +} + + +int webdav_proppatch(pblock *pb, Session *sn, Request *rq) { + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + UcxBuffer *reqbody = rqbody2buffer(sn, rq); + if(!reqbody) { + return REQ_ABORTED; + } + + int error = 0; + WebdavProppatchRequest *proppatch = proppatch_parse( + sn, + rq, + reqbody->space, + reqbody->size, + &error); + ucx_buffer_free(reqbody); + if(!proppatch) { + switch(error) { + // TODO: handle all errors + default: return REQ_ABORTED; + } + } + + WebdavBackend *dav = rq->davCollection ? + rq->davCollection : &default_backend; + + // requested uri and path + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + // The multistatus response object contains responses for all + // requested resources. At the end the Multistatus object will be + // serialized to xml + Multistatus *ms = multistatus_response(sn, rq); + if(!ms) { + return REQ_ABORTED; + } + ms->proppatch = TRUE; + + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + WebdavOperation *op = webdav_create_proppatch_operation( + sn, + rq, + dav, + proppatch, + response); + + int ret = REQ_PROCEED; + + // Execute proppatch + if(webdav_op_proppatch(op, uri, path)) { + ret = REQ_ABORTED; + } + + // send response + if(ret == REQ_PROCEED && multistatus_send(ms, sn->csd)) { + ret = REQ_ABORTED; + // TODO: log error + } else { + // TODO: error response + } + + // cleanup + xmlFreeDoc(proppatch->doc); + + return ret; +} + +int webdav_mkcol(pblock *pb, Session *sn, Request *rq) { + WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE); + if(!op) { + return REQ_ABORTED; + } + + int ret = REQ_ABORTED; + if(!webdav_vfs_op_do(op, WEBDAV_VFS_MKDIR)) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, 201, NULL); + protocol_start_response(sn, rq); + ret = REQ_PROCEED; + } else { + int status_code = 500; + if(op->vfs->vfs_errno == EEXIST) { + // 405 (Method Not Allowed) - MKCOL can only be executed on an unmapped URL. + status_code = 405; + } else if(op->vfs->vfs_errno == ENOENT) { + // 409 (Conflict) - A collection cannot be made at the Request-URI until + // one or more intermediate collections have been created. The server + // MUST NOT create those intermediate collections automatically. + status_code = 409; + } + protocol_status(sn, rq, status_code, NULL); + } + + return ret; +} + +int webdav_post(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +typedef struct DeleteFile { + char *path; + struct stat s; +} DeleteFile; + +typedef struct DeleteLists { + UcxAllocator *a; + UcxList *dirs_begin; + UcxList *dirs_end; + UcxList *files_begin; + UcxList *files_end; +} DeleteOp; + +static int deletelist_add( + VFSContext *vfs, + const char *href, + const char *path, + VFSDir *parent, + struct stat *s, + void *userdata) +{ + DeleteOp *op = userdata; + + // create object for this file + DeleteFile *file = almalloc(op->a, sizeof(DeleteFile)); + if(!file) { + return 1; + } + file->path = sstrdup_a(op->a, sstr((char*)path)).ptr; + if(!file->path) { + return 1; + } + file->s = *s; + + // determine which list to use + UcxList **begin; + UcxList **end; + if(S_ISDIR(s->st_mode)) { + begin = &op->dirs_begin; + end = &op->dirs_end; + } else { + begin = &op->files_begin; + end = &op->files_end; + } + + // add file to list + UcxList *elm = ucx_list_append_a(op->a, NULL, file); + if(!elm) { + alfree(op->a, file->path); // at least do some cleanup, although it + alfree(op->a, file); // isn't really necessary + return 1; + } + if(*begin == NULL) { + *begin = elm; + *end = elm; + } else { + ucx_list_concat(*end, elm); + *end = elm; + } + + return 0; +} + +static int webdav_delete_collection(WebdavVFSOperation *op) +{ + DeleteOp del; + ZERO(&del, sizeof(DeleteOp)); + del.a = session_get_allocator(op->sn); + + // get a list of all files + if(webdav_op_iterate_children(op->vfs, -1, NULL, op->path, + deletelist_add, &del)) + { + return 1; + } + + // add root to list of dir list + DeleteFile root; + root.path = op->path; + root.s = *op->stat; + UcxList root_elm; + root_elm.data = &root; + root_elm.prev = NULL; + root_elm.next = del.dirs_begin; + + if(del.dirs_begin) { + del.dirs_begin->prev = &root_elm; + del.dirs_begin = &root_elm; + } else { + del.dirs_begin = &root_elm; + del.dirs_end = &root_elm; + } + + // delete files first + UCX_FOREACH(elm, del.files_begin) { + DeleteFile *file = elm->data; + WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s); + if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) { + return 1; + } + } + + // delete directories, reverse order + for(UcxList *elm=del.dirs_end;elm;elm=elm->prev) { + DeleteFile *file = elm->data; + WebdavVFSOperation sub = webdav_vfs_sub_op(op, file->path, &file->s); + if(webdav_vfs_op_do(&sub, WEBDAV_VFS_DELETE)) { + return 1; + } + } + + return 0; +} + +int webdav_delete(pblock *pb, Session *sn, Request *rq) { + WebdavVFSOperation *op = webdav_vfs_op(sn, rq, rq->davCollection, TRUE); + if(!op) { + return REQ_ABORTED; + } + + // stat to find out if the resource is a collection + struct stat s; + if(vfs_stat(op->vfs, op->path, &s)) { + sys_set_error_status(op->vfs); + return REQ_ABORTED; + } + op->stat = &s; + + int ret; + if(S_ISDIR(s.st_mode)) { + ret = webdav_delete_collection(op); + } else { + ret = webdav_vfs_op_do(op, WEBDAV_VFS_DELETE); + } + + // send response + if(ret == REQ_PROCEED) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, 204, NULL); + protocol_start_response(sn, rq); + } else { + protocol_status(sn, rq, 500, NULL); + } + + return ret; +} + +int webdav_put(pblock *pb, Session *sn, Request *rq) { + char *path = pblock_findkeyval(pb_key_path, rq->vars); + + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); + return REQ_ABORTED; + } + + struct stat s; + int create_file = 0; + if(vfs_stat(vfs, path, &s)) { + if(vfs->vfs_errno == ENOENT) { + create_file = O_CREAT; + } else { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + } else if(S_ISDIR(s.st_mode)) { + // PUT on collections is not allowed + protocol_status(sn, rq, PROTOCOL_METHOD_NOT_ALLOWED, NULL); + return REQ_ABORTED; + } + + SYS_FILE fd = vfs_open(vfs, path, O_WRONLY | O_TRUNC | create_file); + if(!fd) { + // if it fails, vfs_open sets http status code + return REQ_ABORTED; + } + + // TODO: check permissions, lock, ... + + // all checks done + + char *expect = pblock_findkeyval(pb_key_expect, rq->headers); + if(expect) { + if(!strcasecmp(expect, "100-continue")) { + if(http_send_continue(sn)) { + return REQ_ABORTED; + } + } + } + + char in[4096]; + int r; + while((r = netbuf_getbytes(sn->inbuf, in, 2048)) > 0) { + int w = 0; + while(w < r) { + w += system_fwrite(fd, in, r); + } + } + + system_fclose(fd); + + int status = create_file ? PROTOCOL_CREATED : PROTOCOL_NO_CONTENT; + pblock_nvinsert("content-length", "0", rq->srvhdrs); + protocol_status(sn, rq, status, NULL); + protocol_start_response(sn, rq); + + return REQ_PROCEED; +} + +int webdav_copy(pblock *pb, Session *sn, Request *rq) { + char *path = pblock_findkeyval(pb_key_path, rq->vars); + char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); + + char *destination = pblock_findval("destination", rq->headers); + if(!destination) { + protocol_status(sn, rq, PROTOCOL_BAD_REQUEST, NULL); + return REQ_ABORTED; + } + + VFSContext *vfs = vfs_request_context(sn, rq); + if(!vfs) { + protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); + return REQ_ABORTED; + } + + struct stat src_s; + if(vfs_stat(vfs, path, &src_s)) { + protocol_status(sn, rq, util_errno2status(vfs->vfs_errno), NULL); + return REQ_ABORTED; + } + + // TODO: if src is a directory, make sure the uri has a trailing path separator + + + return REQ_ABORTED; +} + +int webdav_move(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_lock(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_unlock(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_report(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + +int webdav_acl(pblock *pb, Session *sn, Request *rq) { + return REQ_ABORTED; +} + + + +/* ------------------------ default webdav backend ------------------------ */ + +int default_propfind_init( + WebdavPropfindRequest *rq, + const char* path, + const char *href, + WebdavPList **outplist) +{ + DefaultWebdavData *data = pool_malloc( + rq->sn->pool, + sizeof(DefaultWebdavData)); + if(!data) { + return 1; + } + rq->userdata = data; + + data->vfsproperties = webdav_vfs_properties(outplist, TRUE, rq->allprop, 0); + + return 0; +} + +int default_propfind_do( + WebdavPropfindRequest *request, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s) +{ + DefaultWebdavData *data = request->userdata; + + // add all requested vfs properties like getcontentlength ... + if(webdav_add_vfs_properties( + resource, + request->sn->pool, + data->vfsproperties, + s)) + { + return 1; + } + + return 0; +} + +int default_propfind_finish(WebdavPropfindRequest *rq) { + return 0; +} + +int default_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **setInOut, + WebdavPList **removeInOut) +{ + return 0; +} + +int default_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit) +{ + return 0; +} + + + +/* ------------------------------ Utils ------------------------------ */ + +UcxKey webdav_property_key(const char *ns, const char *name) { + UcxKey key; + sstr_t data = ucx_sprintf("%s\n%s", name, ns); + key.data = data.ptr; + key.len = data.length; + key.hash = ucx_hash(data.ptr, data.length); + return key; +} + + + + +/* ------------------------------ public API ------------------------------ */ + +int webdav_getdepth(Request *rq) { + char *depth_str = pblock_findkeyval(pb_key_depth, rq->headers); + int depth = 0; + if(depth_str) { + size_t dlen = strlen(depth_str); + if(!memcmp(depth_str, "infinity", dlen)) { + depth = -1; + } else if(dlen == 1 && depth_str[0] == '1') { + depth = 1; + } + } + return depth; +} + +int webdav_plist_add( + pool_handle_t *pool, + WebdavPList **begin, + WebdavPList **end, + WebdavProperty *prop) +{ + WebdavPList *elm = pool_malloc(pool, sizeof(WebdavPList)); + if(!elm) { + return 1; + } + elm->prev = *end; + elm->next = NULL; + elm->property = prop; + + if(!*begin) { + *begin = elm; + *end = elm; + return 0; + } + + (*end)->next = elm; + *end = elm; + + return 0; +} + +WebdavPList* webdav_plist_clone(pool_handle_t *pool, WebdavPList *list) { + return webdav_plist_clone_s(pool, list, NULL); +} + +WebdavPList* webdav_plist_clone_s( + pool_handle_t *pool, + WebdavPList *list, + size_t *newlen) +{ + WebdavPList *new_list = NULL; // start of the new list + WebdavPList *new_list_end = NULL; // end of the new list + + size_t len = 0; + + WebdavPList *elm = list; + while(elm) { + // copy list item + WebdavPList *new_elm = pool_malloc(pool, sizeof(WebdavPList)); + if(!new_elm) { + if(newlen) *newlen = 0; + return NULL; + } + new_elm->property = elm->property; // new list contains original ptr + new_elm->prev = new_list_end; + new_elm->next = NULL; + + if(new_list_end) { + new_list_end->next = new_elm; + } else { + new_list = new_elm; + } + new_list_end = new_elm; + + len++; + elm = elm->next; + } + + if(newlen) *newlen = len; + return new_list; +} + +size_t webdav_plist_size(WebdavPList *list) { + size_t count = 0; + WebdavPList *elm = list; + while(elm) { + count++; + elm = elm->next; + } + return count; +} + +WebdavPListIterator webdav_plist_iterator(WebdavPList **list) { + WebdavPListIterator i; + i.list = list; + i.cur = NULL; + i.next = *list; + i.index = 0; + return i; +} + +int webdav_plist_iterator_next(WebdavPListIterator *i, WebdavPList **cur) { + if(i->cur) { + i->index++; + } + + i->cur = i->next; + i->next = i->cur ? i->cur->next : NULL; + *cur = i->cur; + + return i->cur != NULL; +} + +void webdav_plist_iterator_remove_current(WebdavPListIterator *i) { + WebdavPList *cur = i->cur; + if(cur->prev) { + cur->prev->next = cur->next; + if(cur->next) { + cur->next->prev = cur->prev; + } + } else { + *i->list = cur->next; + if(cur->next) { + cur->next->prev = NULL; + } + } +} + +int webdav_nslist_add( + pool_handle_t *pool, + WebdavNSList **begin, + WebdavNSList **end, + WSNamespace *ns) +{ + // same as webdav_plist_add but with different type + WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList)); + if(!elm) { + return 1; + } + elm->prev = *end; + elm->next = NULL; + elm->namespace = ns; + + if(!*begin) { + *begin = elm; + *end = elm; + return 0; + } + + (*end)->next = elm; + *end = elm; + + return 0; +} + + +WSNamespace* webdav_dav_namespace(void) { + return &dav_namespace; +} + +WebdavProperty* webdav_resourcetype_collection(void) { + return &dav_resourcetype_collection; +} + +WebdavProperty* webdav_resourcetype_empty(void) { + return &dav_resourcetype_empty; +} + +WebdavProperty* webdav_dav_property( + pool_handle_t *pool, + const char *name) +{ + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + if(!property) { + return NULL; + } + memset(property, 0, sizeof(WebdavProperty)); + + property->namespace = &dav_namespace; + property->name = name; + return property; +} + +int webdav_resource_add_dav_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *name, + const char *str, + size_t len) +{ + WebdavProperty *property = webdav_dav_property(pool, name); + if(!property) { + return 1; + } + + property->name = pool_strdup(pool, name); + if(!property->name) { + return 1; + } + + char *value = pool_malloc(pool, len+1); + if(!value) { + return 1; + } + memcpy(value, str, len); + value[len] = '\0'; + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + return res->addproperty(res, property, 200); +} + +int webdav_resource_add_stringproperty( + WebdavResource *res, + pool_handle_t pool, + const char *xmlns_prefix, + const char *xmlns_href, + const char *name, + const char *str, + size_t len) +{ + WebdavProperty *property = pool_malloc(pool, sizeof(WebdavProperty)); + if(!property) { + return 1; + } + memset(property, 0, sizeof(WebdavProperty)); + + property->name = pool_strdup(pool, name); + if(!property->name) { + return 1; + } + + xmlNs *ns = pool_malloc(pool, sizeof(xmlNs)); + if(!ns) { + return 1; + } + memset(ns, 0, sizeof(xmlNs)); + ns->prefix = (const xmlChar*)pool_strdup(pool, xmlns_prefix); + ns->href = (const xmlChar*)pool_strdup(pool, xmlns_href); + if(!ns->prefix || !ns->href) { + return 1; + } + + char *value = pool_malloc(pool, len+1); + if(!value) { + return 1; + } + memcpy(value, str, len); + value[len] = '\0'; + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + property->value.text.str = value; + property->value.text.length = len; + property->vtype = WS_VALUE_TEXT; + + return res->addproperty(res, property, 200); +} + +int webdav_property_set_value( + WebdavProperty *p, + pool_handle_t *pool, + char *value) +{ + WSXmlNode *node = pool_malloc(pool, sizeof(WSXmlNode)); + if(!node) { + return 1; + } + ZERO(node, sizeof(WSXmlNode)); + + node->content = (xmlChar*)value; + node->type = XML_TEXT_NODE; + + p->value.node = node; + p->vtype = WS_VALUE_XML_NODE; + return 0; +} + +WebdavVFSProperties webdav_vfs_properties( + WebdavPList **plistInOut, + WSBool removefromlist, + WSBool allprop, + uint32_t flags) +{ + WebdavVFSProperties ret; + ZERO(&ret, sizeof(WebdavVFSProperties)); + + WSBool etag = 1; + WSBool creationdate = 1; + + WebdavPListIterator i = webdav_plist_iterator(plistInOut); + WebdavPList *cur; + while(webdav_plist_iterator_next(&i, &cur)) { + WSNamespace *ns = cur->property->namespace; + if(ns && !strcmp((const char*)ns->href, "DAV:")) { + const char *name = cur->property->name; + WSBool remove_prop = removefromlist; + if(!strcmp(name, "getlastmodified")) { + ret.getlastmodified = 1; + } else if(!strcmp(name, "getcontentlength")) { + ret.getcontentlength = 1; + } else if(!strcmp(name, "resourcetype")) { + ret.getresourcetype = 1; + } else if(etag && !strcmp(name, "getetag")) { + ret.getetag = 1; + } else if(creationdate && !strcmp(name, "creationdate")) { + ret.creationdate = 1; + } else { + remove_prop = FALSE; + } + + if(remove_prop) { + webdav_plist_iterator_remove_current(&i); + } + } + } + + if(allprop) { + ret.creationdate = 1; + ret.getcontentlength = 1; + ret.getetag = 1; + ret.getlastmodified = 1; + ret.getresourcetype = 1; + } + + return ret; +} + +int webdav_add_vfs_properties( + WebdavResource *res, + pool_handle_t *pool, + WebdavVFSProperties properties, + struct stat *s) +{ + if(properties.getresourcetype) { + if(S_ISDIR(s->st_mode)) { + res->addproperty(res, &dav_resourcetype_collection, 200); + } else { + res->addproperty(res, &dav_resourcetype_empty, 200); + } + } + if(properties.getcontentlength) { + char *buf = pool_malloc(pool, 64); + if(!buf) { + return 1; + } + uint64_t contentlength = s->st_size; + snprintf(buf, 64, "%" PRIu64, contentlength); + if(webdav_resource_add_dav_stringproperty(res, pool, "getcontentlength", buf, strlen(buf))) { + return 1; + } + } + if(properties.getlastmodified) { + char *buf = pool_malloc(pool, HTTP_DATE_LEN+1); + if(!buf) { + return 1; + } + buf[HTTP_DATE_LEN] = 0; + + struct tm mtms; + struct tm *mtm = system_gmtime(&s->st_mtim.tv_sec, &mtms); + + if(mtm) { + strftime(buf, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm); + if(webdav_resource_add_dav_stringproperty(res, pool, "getlastmodified", buf, strlen(buf))) { + return 1; + } + } else { + return 1; + } + } + if(properties.creationdate) { + // TODO + } + if(properties.getetag) { + char *buf = pool_malloc(pool, 96); + if(!buf) { + return 1; + } + snprintf(buf, + 96, + "\"%x-%x\"", + (int)s->st_size, + (int)s->st_mtim.tv_sec); + if(webdav_resource_add_dav_stringproperty(res, pool, "getetag", buf, strlen(buf))) { + return 1; + } + } + + return 0; +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/webdav.h --- a/src/server/webdav/webdav.h Tue Aug 13 22:14:32 2019 +0200 +++ b/src/server/webdav/webdav.h Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2013 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,16 +30,100 @@ #define WEBDAV_H #include "../public/webdav.h" -#include "../util/strbuf.h" #include #include +#include #ifdef __cplusplus extern "C" { #endif + +typedef struct WebdavType { + webdav_init_func init; + webdav_create_func create; +} WebdavType; + +typedef struct DefaultWebdavData { + WebdavVFSProperties vfsproperties; +} DefaultWebdavData; + +WebdavType* webdav_get_type(scstr_t dav_class); + +void* webdav_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, WebdavType *dav_class, WSConfigNode *config, int *error); + +int webdav_init(pblock *pb, Session *sn, Request *rq); + +int webdav_service(pblock *pb, Session *sn, Request *rq); + +/* + * returns a buffer containing the request body + * + * this function sets an http response code in case of an error + * or missing request body + */ +UcxBuffer* rqbody2buffer(Session *sn, Request *rq); +int webdav_options(pblock *pb, Session *sn, Request *rq); + +int webdav_propfind(pblock *pb, Session *sn, Request *rq); + +int webdav_propfind_init( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + const char *path, + const char *uri, + UcxList **out_req); + +int webdav_propfind_do( + WebdavBackend *dav, + WebdavPropfindRequest *propfind, + WebdavResponse *response, + VFSContext *vfs, + char *path, + char *uri); + + +int webdav_proppatch(pblock *pb, Session *sn, Request *rq); +int webdav_mkcol(pblock *pb, Session *sn, Request *rq); +int webdav_post(pblock *pb, Session *sn, Request *rq); +int webdav_delete(pblock *pb, Session *sn, Request *rq); +int webdav_put(pblock *pb, Session *sn, Request *rq); +int webdav_copy(pblock *pb, Session *sn, Request *rq); +int webdav_move(pblock *pb, Session *sn, Request *rq); +int webdav_lock(pblock *pb, Session *sn, Request *rq); +int webdav_unlock(pblock *pb, Session *sn, Request *rq); +int webdav_report(pblock *pb, Session *sn, Request *rq); +int webdav_acl(pblock *pb, Session *sn, Request *rq); +int webdav_search (pblock *pb, Session *sn, Request *rq); + + +int default_propfind_init( + WebdavPropfindRequest *rq, + const char *path, + const char *href, + WebdavPList **outplist); +int default_propfind_do( + WebdavPropfindRequest *request, + WebdavResponse *response, + VFS_DIR parent, + WebdavResource *resource, + struct stat *s); +int default_propfind_finish(WebdavPropfindRequest *rq); +int default_proppatch_do( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WebdavPList **setInOut, + WebdavPList **removeInOut); +int default_proppatch_finish( + WebdavProppatchRequest *request, + WebdavResource *response, + VFSFile *file, + WSBool commit); + +UcxKey webdav_property_key(const char *ns, const char *name); #ifdef __cplusplus } diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/xml.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/xml.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,734 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include + +#include "../util/util.h" +#include "../util/pool.h" + +#include "xml.h" + +/***************************************************************************** + * Utility functions + *****************************************************************************/ + +/* + * generates a string key for an xml namespace + * format: prefix '\0' href + */ +static sstr_t xml_namespace_key(UcxAllocator *a, WSNamespace *ns) { + sstr_t key = sstrcat_a(a, 3, + ns->prefix ? sstr((char*)ns->prefix) : S("\0"), + S("\0"), + sstr((char*)ns->href)); + return key; +} + + +/***************************************************************************** + * Public functions + *****************************************************************************/ + +/* ------------------------ wsxml_iterator ------------------------ */ + +typedef struct StackElm { + WSXmlNode *node; // list of nodes + //WSXmlNode *parent; // if not NULL, call endcb after node->next is NULL + int endonly; + struct StackElm *next; +} StackElm; + +#define STACK_PUSH(stack, elm) if(stack) { elm->next = stack; } stack = elm; + +int wsxml_iterator( + pool_handle_t *pool, + WSXmlNode *node, + wsxml_func begincb, + wsxml_func endcb, + void *udata) +{ + if(!node) { + return 0; + } + + StackElm *stack = pool_malloc(pool, sizeof(StackElm)); + if(!stack) { + return 1; // OOM + } + stack->next = NULL; + stack->node = node; + stack->endonly = 0; + //stack->parent = NULL; + + int ret = 0; + int br = 0; + while(stack) { + StackElm *cur = stack; + WSXmlNode *xmlnode = cur->node; // get top stack element + stack = cur->next; // and remove it + cur->next = NULL; + + while(xmlnode && !cur->endonly) { + // element begin callback + if(begincb(xmlnode, udata)) { + br = 1; + break; // I don't like break with labels - is this wrong? + } + + if(xmlnode->children) { + // put the children on the stack + // the next stack iteration will process the children + StackElm *newelm = pool_malloc(pool, sizeof(StackElm)); + if(!newelm) { + ret = 1; + br = 1; + break; + } + newelm->next = NULL; + newelm->node = xmlnode->children; + // setting the parent will make sure endcb will be called + // for the current xmlnode after all children are processed + //newelm->parent = xmlnode; + newelm->endonly = 0; + + // if xmlnode->next is not NULL, there are still nodes at + // this level, therefore we have to put these also on the + // stack + // this way, the remaining nodes are processed after all + // children and the end tag are processed + if(xmlnode->next) { + StackElm *nextelm = pool_malloc(pool, sizeof(StackElm)); + if(!nextelm) { + ret = 1; + br = 1; + break; + } + nextelm->node = xmlnode->next; + nextelm->next = NULL; + nextelm->endonly = 0; + STACK_PUSH(stack, nextelm); + } + + // we have to put the end tag of the current element + // on the stack to ensure endcb is called for the current + // element, after all children are processed + // reuse cur + cur->node = xmlnode; + cur->endonly = 1; + STACK_PUSH(stack, cur); + + cur = NULL; + + // now we can put the children on the stack + STACK_PUSH(stack, newelm); + // break, because we don't want to process xmlnode->next now + break; + } else { + // no children means, the end callback can be called directly + // after the begin callback (no intermediate nodes) + cur->node = NULL; + if(endcb(xmlnode, udata)) { + br = 1; + break; + } + } + + // continue with next node at this level + xmlnode = xmlnode->next; + } + if(br) { + break; // break because of an error + } + + if(cur && cur->node) { + //xmlNode *endNode = cur->parent ? cur->parent : cur->node; + xmlNode *endNode = cur->node; + if(endcb(endNode, udata)) { + break; + } + pool_free(pool, cur); + } + } + + // free all remaining elements + StackElm *elm = stack; + while(elm) { + StackElm *next = elm->next; + pool_free(pool, elm); + elm = next; + } + + return ret; +} + +/* ------------------- wsxml_get_required_namespaces ------------------- */ + +typedef struct WSNsCollector { + UcxAllocator *a; + UcxMap *nsmap; + WebdavNSList *def; + int error; +} WSNsCollector; + +static int nslist_node_begin(xmlNode *node, void *userdata) { + WSNsCollector *col = userdata; + // namespace required for all elements + if(node->type == XML_ELEMENT_NODE && node->ns) { + // we create a list of unique prefix-href namespaces by putting + // all namespaces in a map + sstr_t nskey = xml_namespace_key(col->a, node->ns); + if(!nskey.ptr) { + col->error = 1; + return 1; + } + if(ucx_map_sstr_put(col->nsmap, nskey, node->ns)) { + col->error = 1; + return 1; + } + + // collect all namespace definitions for removing these namespaces + // from col->nsmap later + WSNamespace *def = node->nsDef; + while(def) { + WebdavNSList *newdef = col->a->malloc( + col->a->pool, sizeof(WebdavNSList)); + if(!newdef) { + col->error = 1; + return 1; + } + newdef->namespace = def; + newdef->prev = NULL; + newdef->next = NULL; + // prepend newdef to the list + if(col->def) { + newdef->next = col->def; + col->def->prev = newdef; + } + col->def = newdef; + + // continue with next namespace definition + def = def->next; + } + } + return 0; +} + +static int nslist_node_end(xmlNode *node, void *userdata) { + return 0; +} + +WebdavNSList* wsxml_get_required_namespaces( + pool_handle_t *pool, + WSXmlNode *node, + int *error) +{ + if(error) *error = 0; + + UcxAllocator a = util_pool_allocator(pool); + UcxMap *nsmap = ucx_map_new_a(&a, 16); + if(!nsmap) { + if(error) *error = 1; + return NULL; + } + + WSNsCollector col; + col.a = &a; + col.nsmap = nsmap; + col.def = NULL; + + // iterate over all xml elements + // this will fill the hashmap with all namespaces + // all namespace definitions are added to col.def + WebdavNSList *list = NULL; + WebdavNSList *end = NULL; + if(wsxml_iterator(pool, node, nslist_node_begin, nslist_node_end, &col)) { + if(error) *error = 1; + } else { + // remove all namespace definitions from the map + // what we get is a map that contains all missing namespace definitions + WebdavNSList *def = col.def; + while(def) { + sstr_t nskey = xml_namespace_key(&a, def->namespace); + if(!nskey.ptr) { + if(error) *error = 1; + break; + } + ucx_map_sstr_remove(nsmap, nskey); + def = def->next; + } + + // convert nsmap to a list + UcxMapIterator i = ucx_map_iterator(nsmap); + WSNamespace *ns; + UCX_MAP_FOREACH(key, ns, i) { + WebdavNSList *newelm = pool_malloc(pool, sizeof(WebdavNSList)); + if(!newelm) { + if(error) *error = 1; + list = NULL; + break; + } + newelm->namespace = ns; + newelm->next = NULL; + newelm->prev = end; // NULL or the end of list + if(end) { + end->next = newelm; // append new element + } else { + list = newelm; // start new list + } + end = newelm; + } + } + + ucx_map_free(nsmap); + return list; +} + + +static ssize_t buf_writefunc(void *buf, const char *s, size_t len) { + int w = ucx_buffer_write(s, 1, len, buf); + return w == 0 ? IO_ERROR : w; +} + +WSXmlData* wsxml_node2data( + pool_handle_t *pool, + WSXmlNode *node) +{ + UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + if(!buf) { + return NULL; + } + + int error = 0; + WebdavNSList *nslist = wsxml_get_required_namespaces(pool, node, &error); + if(error) { + return NULL; + } + + Writer writer; + char buffer[512]; + writer_init_with_stream(&writer, buf, buf_writefunc, buffer, 512); + + WSXmlData *data = NULL; + if(!wsxml_write_nodes(pool, &writer, NULL, node) && !writer_flush(&writer)) { + data = pool_malloc(pool, sizeof(WSXmlData)); + if(data) { + data->data = pool_malloc(pool, buf->size + 1); + if(data->data) { + memcpy(data->data, buf->space, buf->size); + data->data[buf->size] = '\0'; + data->length = buf->size; + data->namespaces = nslist; + } + } + } + + ucx_buffer_free(buf); + + return data; +} + +char* wsxml_nslist2string(pool_handle_t *pool, WebdavNSList *nslist) { + if(!nslist) return NULL; + + // get required string length + size_t len = 0; + WebdavNSList *elm = nslist; + while(elm) { + WSNamespace *ns = elm->namespace; + if(ns) { + if(ns->prefix) len += strlen((const char*)ns->prefix); + if(ns->href) len += strlen((const char*)ns->href); + len += 2; // 1 char for ':', 1 char for \n or \0 + } + elm = elm->next; + } + + // alloc string + char *str = pool_malloc(pool, len); + if(!str) { + return NULL; + } + char *pos = str; + + // copy namespace definitions to the string + elm = nslist; + while(elm) { + WSNamespace *ns = elm->namespace; + if(ns) { + if(ns->prefix) { + size_t prefixlen = strlen((const char*)ns->prefix); + memcpy(pos, ns->prefix, prefixlen); + pos[prefixlen] = ':'; + pos += prefixlen + 1; + } else { + pos[0] = ':'; + pos++; + } + if(ns->href) { + size_t hreflen = strlen((const char*)ns->href); + memcpy(pos, ns->href, hreflen); + pos[hreflen] = elm->next ? '\n' : '\0'; + pos += hreflen + 1; + } else { + pos[0] = elm->next ? '\n' : '\0'; + pos++; + } + } + elm = elm->next; + } + + return str; +} + +WebdavNSList* wsxml_string2nslist(pool_handle_t *pool, char *nsliststr) { + if(!nsliststr) return NULL; + size_t len = strlen(nsliststr); + WebdavNSList *list_start = NULL; + WebdavNSList *list_current = NULL; + + char *prefix = nsliststr; + size_t prefix_start = 0; + size_t prefix_len = 0; + char *href = NULL; + size_t href_start = len; + size_t i; + for(i=0;i<=len;i++) { + char c = nsliststr[i]; + if(c == '\n' || c == '\0') { + if(i > href_start) { + WebdavNSList *elm = pool_malloc(pool, sizeof(WebdavNSList)); + if(!elm) { + break; + } + elm->prev = list_current; + elm->next = NULL; + WSNamespace *ns = pool_malloc(pool, sizeof(WSNamespace)); + elm->namespace = ns; + if(!ns) { + break; + } + memset(ns, 0, sizeof(WSNamespace)); + ns->prefix = prefix_len > 0 ? (xmlChar*)sstrdup_pool(pool, sstrn(prefix, prefix_len)).ptr : NULL; + ns->href = (xmlChar*)sstrdup_pool(pool, sstrn(href, i-href_start)).ptr; + if(list_current) { + list_current->next = elm; + } else { + list_start = elm; + } + list_current = elm; + } + prefix_start = i + 1; + prefix = nsliststr + prefix_start; + prefix_len = 0; + href_start = len; + href = NULL; + } else if(!href && c == ':') { + prefix_len = i - prefix_start; + href_start = i + 1; + href = nsliststr + href_start; + } + } + + if(i < len) { + // error, cleanup + while(list_start) { + if(list_start->namespace) { + WSNamespace *ns = list_start->namespace; + if(ns->prefix) { + pool_free(pool, (char*)ns->prefix); + } + if(ns->href) { + pool_free(pool, (char*)ns->href); + } + pool_free(pool, ns); + } + WebdavNSList *next = list_start->next; + pool_free(pool, list_start); + list_start = next; + } + list_start = NULL; + } + + return list_start; +} + +/***************************************************************************** + * Non public functions + *****************************************************************************/ + +typedef struct XmlWriter { + /* + * Memory pool for temp memory allocations + */ + pool_handle_t *pool; + + /* + * Buffered output stream + */ + Writer *out; + + /* + * Map for all previously defined namespaces + * key: (char*) namespace prefix + * value: WSNamespace* + */ + UcxMap *namespaces; + + /* + * Should namespace definitions be created + */ + WSBool define_namespaces; +} XmlWriter; + +/* + * Serialize an XML text node + * This replaces some special characters with entity refs + * type: 0 = element text, 1 = attribute text + */ +static void xml_ser_text(Writer *out, int type, const char *text) { + size_t start = 0; + size_t i; + sstr_t entityref = { NULL, 0 }; + for(i=0;text[i]!='\0';i++) { + char c = text[i]; + if(c == '&') { + entityref = S("&"); + } else if(type == 0) { + if(c == '<') { + entityref = S("<"); + } else if(c == '>') { + entityref = S(">"); + } + } else { + if(c == '\"') { + entityref = S("""); + } else if(c == '\'') { + entityref = S("'"); + } + } + + if(entityref.ptr) { + size_t len = i-start; + if(len > 0) { + writer_put(out, text+start, len); + } + writer_puts(out, entityref); + entityref.ptr = NULL; + entityref.length = 0; + start = i+1; + } + } + size_t len = i-start; + if(len > 0) { + writer_put(out, text+start, len); + } +} + +/* + * Serialize an XML element node + */ +static void xml_ser_element(XmlWriter *xw, xmlNode *node) { + Writer *out = xw->out; + writer_putc(out, '<'); + + // write prefix and ':' + if(node->ns && node->ns->prefix) { + writer_puts(out, sstr((char*)node->ns->prefix)); + writer_putc(out, ':'); + } + + // node name + writer_puts(out, sstr((char*)node->name)); + + // namespace definitions + if(xw->define_namespaces) { + xmlNs *nsdef = node->nsDef; + while(nsdef) { + // we define only namespaces without prefix or namespaces + // with prefix, that are not already defined + // xw->namespaces contains all namespace, that were defined + // before xml serialization + if(!nsdef->prefix) { + writer_puts(out, S(" xmlns=\"")); + writer_puts(out, sstr((char*)nsdef->href)); + writer_putc(out, '"'); + } else { + WSNamespace *n = xw->namespaces ? + ucx_map_cstr_get(xw->namespaces, (char*)nsdef->prefix) : + NULL; + if(!n) { + writer_puts(out, S(" xmlns:")); + writer_puts(out, sstr((char*)nsdef->prefix)); + writer_puts(out, S("=\"")); + writer_puts(out, sstr((char*)nsdef->href)); + writer_putc(out, '"'); + } + } + + nsdef = nsdef->next; + } + } + + // attributes + xmlAttr *attr = node->properties; + while(attr) { + // format: ' [:]=""' + writer_putc(out, ' '); + // optional namespace + if(attr->ns && attr->ns->prefix) { + writer_puts(out, sstr((char*)attr->ns->prefix)); + writer_putc(out, ':'); + } + // =" + writer_puts(out, sstr((char*)attr->name)); + writer_puts(out, S("=\"")); + // value + xmlNode *value = attr->children; + while(value) { + if(value->content) { + xml_ser_text(out, 1, (const char*)value->content); + } + value = value->next; + } + // trailing quote + writer_putc(out, '"'); + + attr = attr->next; + } + + if(node->children) { + writer_putc(out, '>'); + } else { + writer_puts(out, S("/>")); + } +} + +static int xml_ser_node_begin(xmlNode *node, void *userdata) { + XmlWriter *xw = userdata; + switch(node->type) { + case XML_ELEMENT_NODE: xml_ser_element(xw, node); break; + case XML_ATTRIBUTE_NODE: break; + case XML_TEXT_NODE: { + xml_ser_text(xw->out, 0, (const char*)node->content); + break; + } + case XML_CDATA_SECTION_NODE: { + break; + } + case XML_ENTITY_REF_NODE: break; + case XML_ENTITY_NODE: break; + case XML_PI_NODE: break; + case XML_COMMENT_NODE: break; + case XML_DOCUMENT_NODE: break; + case XML_DOCUMENT_TYPE_NODE: break; + case XML_DOCUMENT_FRAG_NODE: break; + case XML_NOTATION_NODE: break; + case XML_HTML_DOCUMENT_NODE: break; + case XML_DTD_NODE: break; + case XML_ELEMENT_DECL: break; + case XML_ATTRIBUTE_DECL: break; + case XML_ENTITY_DECL: break; + case XML_NAMESPACE_DECL: break; + case XML_XINCLUDE_START: break; + case XML_XINCLUDE_END: break; + default: break; + } + return 0; +} + +static int xml_ser_node_end(xmlNode *node, void *userdata) { + XmlWriter *xw = userdata; + Writer *out = xw->out; + if(node->type == XML_ELEMENT_NODE) { + if(node->children) { + writer_puts(xw->out, S("ns && node->ns->prefix) { + writer_puts(out, sstr((char*)node->ns->prefix)); + writer_putc(out, ':'); + } + // name and close tag + writer_puts(out, sstr((char*)node->name)); + writer_putc(out, '>'); + + } // element was already closed in xml_ser_node_begin + } + return 0; +} + + +static int xml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + WSBool createdefs, + xmlNode *node) +{ + XmlWriter xmlwriter; + xmlwriter.pool = pool; + xmlwriter.out = out; + xmlwriter.namespaces = nsdefs; + xmlwriter.define_namespaces = createdefs; + + // iterate over xml nodes + // this includes node->children and node->next + int err = wsxml_iterator( + pool, + node, + xml_ser_node_begin, + xml_ser_node_end, + &xmlwriter); + if(err) { + return -1; + } + + return out->error; +} + +int wsxml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + xmlNode *node) +{ + return xml_write_nodes(pool, out, nsdefs, TRUE, node); +} + +int wsxml_write_nodes_without_nsdef( + pool_handle_t *pool, + Writer *out, + xmlNode *node) +{ + return xml_write_nodes(pool, out, NULL, FALSE, node); +} diff -r 21274e5950af -r a1f4cb076d2f src/server/webdav/xml.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/webdav/xml.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBDAV_XML_H +#define WEBDAV_XML_H + +#include "../public/webdav.h" +#include + +#include +#include + +#include "../util/writer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Writes the xmlNode, all children and following nodes to the writer 'out'. + */ +int wsxml_write_nodes( + pool_handle_t *pool, + Writer *out, + UcxMap *nsdefs, + xmlNode *node); + +/* + * Writes the xmlNode, all children and following nodes to the writer 'out' + * without creating any namespace definitions. Therefore all namespaces must + * be already defined and previously written to 'out'. + */ +int wsxml_write_nodes_without_nsdef( + pool_handle_t *pool, + Writer *out, + xmlNode *node); + + +#ifdef __cplusplus +} +#endif + +#endif /* WEBDAV_XML_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/tools/Makefile --- a/src/tools/Makefile Tue Aug 13 22:14:32 2019 +0200 +++ b/src/tools/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -30,7 +30,7 @@ include $(BUILD_ROOT)/config.mk -CFLAGS += -I.. +CFLAGS += -I../ucx/ LDFLAGS += -L../../build/lib -lucx -lwscfg # list of source files diff -r 21274e5950af -r a1f4cb076d2f src/tools/wstool.c --- a/src/tools/wstool.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/tools/wstool.c Sat Sep 24 16:26:10 2022 +0200 @@ -32,7 +32,7 @@ #include #include -#include "../server/config/serverconf.h" +#include "../server/config/serverconfig.h" #include "srvctrlsocket.h" @@ -61,8 +61,8 @@ } int tool_get_tmpdir(char *configfile) { - ServerConfig *serverconf = load_server_config(configfile); - UcxList *list = ucx_map_sstr_get(serverconf->objects, sstrn("Runtime", 7)); + ServerConfig *serverconf = serverconfig_load(configfile); + UcxList *list = serverconfig_get_node_list(serverconf->root, CONFIG_NODE_OBJECT, SC("Runtime")); if(!list) { fprintf(stderr, "Error: No Runtime element in %s\n", configfile); return -1; @@ -71,8 +71,11 @@ fprintf(stderr, "Error: Multiple Runtime elements in %s\n", configfile); return -1; } - ServerConfigObject *runtime = list->data; - sstr_t tmp = cfg_directivelist_get_str(runtime->directives, sstr("Temp")); + ConfigNode *runtime = list->data; + scstr_t tmp = serverconfig_directive_value(runtime, SC("Temp")); + + ucx_list_free(list); + if(!tmp.ptr) { fprintf(stderr, "Error: No Temp directive in Runtime Object\n"); return -1; diff -r 21274e5950af -r a1f4cb076d2f src/ucx/Makefile --- a/src/ucx/Makefile Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/Makefile Sat Sep 24 16:26:10 2022 +0200 @@ -31,7 +31,8 @@ include $(BUILD_ROOT)/config.mk # list of source files -SRC = utils.c +SRC = ucx.c +SRC += utils.c SRC += list.c SRC += map.c SRC += avl.c @@ -43,6 +44,7 @@ SRC += logging.c SRC += buffer.c SRC += stack.c +SRC += array.c OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/ucx/%$(OBJ_EXT)) diff -r 21274e5950af -r a1f4cb076d2f src/ucx/allocator.c --- a/src/ucx/allocator.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/allocator.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,10 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "ucx/allocator.h" + #include -#include "allocator.h" -UcxAllocator default_allocator = { +static UcxAllocator default_allocator = { NULL, ucx_default_malloc, ucx_default_calloc, diff -r 21274e5950af -r a1f4cb076d2f src/ucx/allocator.h --- a/src/ucx/allocator.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,206 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Allocator for custom memory management. - * - * A UCX allocator consists of a pointer to the memory area / pool and four - * function pointers to memory management functions operating on this memory - * area / pool. These functions shall behave equivalent to the standard libc - * functions malloc(), calloc(), realloc() and free(). - * - * The signature of the memory management functions is based on the signature - * of the respective libc function but each of them takes the pointer to the - * memory area / pool as first argument. - * - * As the pointer to the memory area / pool can be arbitrarily chosen, any data - * can be provided to the memory management functions. A UcxMempool is just - * one example. - * - * @see mempool.h - * @see UcxMap - * - * @file allocator.h - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_ALLOCATOR_H -#define UCX_ALLOCATOR_H - -#include "ucx.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * A function pointer to the allocators malloc() function. - * @see UcxAllocator - */ -typedef void*(*ucx_allocator_malloc)(void *pool, size_t n); - -/** - * A function pointer to the allocators calloc() function. - * @see UcxAllocator - */ -typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size); - -/** - * A function pointer to the allocators realloc() function. - * @see UcxAllocator - */ -typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n); - -/** - * A function pointer to the allocators free() function. - * @see UcxAllocator - */ -typedef void(*ucx_allocator_free)(void *pool, void *data); - -/** - * UCX allocator data structure containing memory management functions. - */ -typedef struct { - /** Pointer to an area of memory or a complex memory pool. - * This pointer will be passed to any memory management function as first - * argument. - */ - void *pool; - /** - * The malloc() function for this allocator. - */ - ucx_allocator_malloc malloc; - /** - * The calloc() function for this allocator. - */ - ucx_allocator_calloc calloc; - /** - * The realloc() function for this allocator. - */ - ucx_allocator_realloc realloc; - /** - * The free() function for this allocator. - */ - ucx_allocator_free free; -} UcxAllocator; - -/** - * Returns a pointer to the default allocator. - * - * The default allocator contains wrappers to the standard libc memory - * management functions. Use this function to get a pointer to a globally - * available allocator. You may also define an own UcxAllocator by assigning - * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable - * to any function that takes a UcxAllocator as argument. Note that using - * this function is the recommended way of passing a default allocator, thus - * it never runs out of scope. - * - * @return a pointer to the default allocator - * - * @see UCX_ALLOCATOR_DEFAULT - */ -UcxAllocator *ucx_default_allocator(); - -/** - * A wrapper for the standard libc malloc() function. - * @param ignore ignored (may be used by allocators for pooled memory) - * @param n argument passed to malloc() - * @return return value of malloc() - */ -void *ucx_default_malloc(void *ignore, size_t n); -/** - * A wrapper for the standard libc calloc() function. - * @param ignore ignored (may be used by allocators for pooled memory) - * @param n argument passed to calloc() - * @param size argument passed to calloc() - * @return return value of calloc() - */ -void *ucx_default_calloc(void *ignore, size_t n, size_t size); -/** - * A wrapper for the standard libc realloc() function. - * @param ignore ignored (may be used by allocators for pooled memory) - * @param data argumend passed to realloc() - * @param n argument passed to realloc() - * @return return value of realloc() - */ -void *ucx_default_realloc(void *ignore, void *data, size_t n); -/** - * A wrapper for the standard libc free() function. - * @param ignore ignored (may be used by allocators for pooled memory) - * @param data argument passed to free() - */ -void ucx_default_free(void *ignore, void *data); - -/** - * Shorthand for calling an allocators malloc function. - * @param allocator the allocator to use - * @param n size of space to allocate - * @return a pointer to the allocated memory area - */ -#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n)) - -/** - * Shorthand for calling an allocators calloc function. - * @param allocator the allocator to use - * @param n the count of elements the space should be allocated for - * @param size the size of each element - * @return a pointer to the allocated memory area - */ -#define alcalloc(allocator, n, size) \ - ((allocator)->calloc((allocator)->pool, n, size)) - -/** - * Shorthand for calling an allocators realloc function. - * @param allocator the allocator to use - * @param ptr the pointer to the memory area that shall be reallocated - * @param n the new size of the allocated memory area - * @return a pointer to the reallocated memory area - */ -#define alrealloc(allocator, ptr, n) \ - ((allocator)->realloc((allocator)->pool, ptr, n)) - -/** - * Shorthand for calling an allocators free function. - * @param allocator the allocator to use - * @param ptr the pointer to the memory area that shall be freed - */ -#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr)) - -/** - * Convenient macro for a default allocator struct definition. - */ -#define UCX_ALLOCATOR_DEFAULT {NULL, \ - ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \ - ucx_default_free } - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_ALLOCATOR_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/array.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/array.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,467 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE /* we want to use qsort_r(), if available */ +#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */ + + +#include "ucx/array.h" +#include "ucx/utils.h" + +#include +#include +#include + +#ifndef UCX_ARRAY_DISABLE_QSORT +#ifdef __GLIBC__ +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8) +#define ucx_array_sort_impl qsort_r +#endif /* glibc version >= 2.8 */ +#elif /* not __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__) +#define ucx_array_sort_impl ucx_qsort_r +#define USE_UCX_QSORT_R +#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun) +#if __STDC_VERSION__ >= 201112L +#define ucx_array_sort_impl qsort_s +#endif +#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */ +#endif /* UCX_ARRAY_DISABLE_QSORT */ + +#ifndef ucx_array_sort_impl +#define ucx_array_sort_impl ucx_mergesort +#endif + +static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) { + size_t required_capacity = array->capacity; + while (reqcap > required_capacity) { + if (required_capacity * 2 < required_capacity) + return 1; + required_capacity <<= 1; + } + if (ucx_array_reserve(array, required_capacity)) { + return 1; + } + return 0; +} + +int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t elmsize, size_t index, void* data) { + + if(!alloc || !capacity || !array) { + errno = EINVAL; + return 1; + } + + size_t newcapacity = *capacity; + while(index >= newcapacity) { + if(ucx_szmul(newcapacity, 2, &newcapacity)) { + errno = EOVERFLOW; + return 1; + } + } + + size_t memlen, offset; + if(ucx_szmul(newcapacity, elmsize, &memlen)) { + errno = EOVERFLOW; + return 1; + } + /* we don't need to check index*elmsize - it is smaller than memlen */ + + + void* newptr = alrealloc(alloc, *array, memlen); + if(newptr == NULL) { + errno = ENOMEM; /* we cannot assume that every allocator sets this */ + return 1; + } + *array = newptr; + *capacity = newcapacity; + + + char* dest = *array; + dest += elmsize*index; + memcpy(dest, data, elmsize); + + return 0; +} + +int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t index, void* data) { + + return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*), + index, &data); +} + +UcxArray* ucx_array_new(size_t capacity, size_t elemsize) { + return ucx_array_new_a(capacity, elemsize, ucx_default_allocator()); +} + +UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + UcxArray* array = almalloc(allocator, sizeof(UcxArray)); + if(array) { + ucx_array_init_a(array, capacity, elemsize, allocator); + } + return array; +} + +void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) { + ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator()); +} + +void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + + array->allocator = allocator; + array->elemsize = elemsize; + array->size = 0; + array->data = alcalloc(allocator, capacity, elemsize); + + if (array->data) { + array->capacity = capacity; + } else { + array->capacity = 0; + } +} + +int ucx_array_clone(UcxArray* dest, UcxArray const* src) { + if (ucx_array_ensurecap(dest, src->capacity)) { + return 1; + } + + dest->elemsize = src->elemsize; + dest->size = src->size; + + if (dest->data) { + memcpy(dest->data, src->data, src->size*src->elemsize); + } + + return 0; +} + +int ucx_array_equals(UcxArray const *array1, UcxArray const *array2, + cmp_func cmpfnc, void* data) { + + if (array1->size != array2->size || array1->elemsize != array2->elemsize) { + return 0; + } else { + if (array1->size == 0) + return 1; + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array1->elemsize; + data = &elemsize; + } + + for (size_t i = 0 ; i < array1->size ; i++) { + int r = cmpfnc( + ucx_array_at(array1, i), + ucx_array_at(array2, i), + data); + if (r != 0) + return 0; + } + return 1; + } +} + +void ucx_array_destroy(UcxArray *array) { + if(array->data) + alfree(array->allocator, array->data); + array->data = NULL; + array->capacity = array->size = 0; +} + +void ucx_array_free(UcxArray *array) { + ucx_array_destroy(array); + alfree(array->allocator, array); +} + +int ucx_array_append_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + void* dest = ucx_array_at(array, array->size); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + if (array->size > 0) { + void *dest = ucx_array_at(array, count); + memmove(dest, array->data, array->elemsize*array->size); + } + + if (data) { + memcpy(array->data, data, array->elemsize*count); + } else { + memset(array->data, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_set_from(UcxArray *array, size_t index, + void *data, size_t count) { + if (ucx_array_ensurecap(array, index + count)) + return 1; + + if (index+count > array->size) { + array->size = index+count; + } + + void *dest = ucx_array_at(array, index); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + + return 0; +} + +int ucx_array_concat(UcxArray *array1, const UcxArray *array2) { + + if (array1->elemsize != array2->elemsize) + return 1; + + size_t capacity = array1->capacity+array2->capacity; + + if (array1->capacity < capacity) { + if (ucx_array_reserve(array1, capacity)) { + return 1; + } + } + + void* dest = ucx_array_at(array1, array1->size); + memcpy(dest, array2->data, array2->size*array2->elemsize); + + array1->size += array2->size; + + return 0; +} + +void *ucx_array_at(UcxArray const *array, size_t index) { + char* memory = array->data; + char* loc = memory + index*array->elemsize; + return loc; +} + +size_t ucx_array_find(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array->elemsize; + data = &elemsize; + } + + if (array->size > 0) { + for (size_t i = 0 ; i < array->size ; i++) { + void* ptr = ucx_array_at(array, i); + if (cmpfnc(ptr, elem, data) == 0) { + return i; + } + } + return array->size; + } else { + return 0; + } +} + +int ucx_array_contains(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + return ucx_array_find(array, elem, cmpfnc, data) != array->size; +} + +static void ucx_mergesort_merge(void *arrdata,size_t elemsize, + cmp_func cmpfnc, void *data, + size_t start, size_t mid, size_t end) { + + char* array = arrdata; + + size_t rightstart = mid + 1; + + if (cmpfnc(array + mid*elemsize, + array + rightstart*elemsize, data) <= 0) { + /* already sorted */ + return; + } + + /* we need memory for one element */ + void *value = malloc(elemsize); + + while (start <= mid && rightstart <= end) { + if (cmpfnc(array + start*elemsize, + array + rightstart*elemsize, data) <= 0) { + start++; + } else { + /* save the value from the right */ + memcpy(value, array + rightstart*elemsize, elemsize); + + /* shift all left elements one element to the right */ + size_t shiftcount = rightstart-start; + void *startptr = array + start*elemsize; + void *dest = array + (start+1)*elemsize; + memmove(dest, startptr, shiftcount*elemsize); + + /* bring the first value from the right to the left */ + memcpy(startptr, value, elemsize); + + start++; + mid++; + rightstart++; + } + } + + /* free the temporary memory */ + free(value); +} + +static void ucx_mergesort_impl(void *arrdata, size_t elemsize, + cmp_func cmpfnc, void *data, size_t l, size_t r) { + if (l < r) { + size_t m = l + (r - l) / 2; + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); + ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r); + } +} + +static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1); +} + +#ifdef USE_UCX_QSORT_R +struct cmpfnc_swapargs_info { + cmp_func func; + void *data; +}; + +static int cmp_func_swap_args(void *data, const void *x, const void *y) { + struct cmpfnc_swapargs_info* info = data; + return info->func(x, y, info->data); +} + +static void ucx_qsort_r(void *array, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + struct cmpfnc_swapargs_info info; + info.func = cmpfnc; + info.data = data; + qsort_r(array, count, elemsize, &info, cmp_func_swap_args); +} +#endif /* USE_UCX_QSORT_R */ + +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) { + ucx_array_sort_impl(array->data, array->size, array->elemsize, + cmpfnc, data); +} + +void ucx_array_remove(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, index+1); + memmove(dest, src, (array->size - index)*array->elemsize); + } +} + +void ucx_array_remove_fast(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, array->size); + memcpy(dest, src, array->elemsize); + } +} + +int ucx_array_shrink(UcxArray* array) { + void* newptr = alrealloc(array->allocator, array->data, + array->size*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = array->size; + return 0; + } else { + return 1; + } +} + +int ucx_array_resize(UcxArray* array, size_t capacity) { + if (array->capacity >= capacity) { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + if (array->size > array->capacity) { + array->size = array->capacity; + } + return 0; + } else { + return 1; + } + } else { + return ucx_array_reserve(array, capacity); + } +} + +int ucx_array_reserve(UcxArray* array, size_t capacity) { + if (array->capacity > capacity) { + return 0; + } else { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + return 0; + } else { + return 1; + } + } +} + +int ucx_array_grow(UcxArray* array, size_t count) { + return ucx_array_reserve(array, array->size+count); +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/avl.c --- a/src/ucx/avl.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/avl.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,7 +26,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "avl.h" +#include "ucx/avl.h" + +#include #define ptrcast(ptr) ((void*)(ptr)) #define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree)) @@ -134,6 +136,23 @@ alfree(al, tree); } +static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node, + ucx_destructor destr) { + if (node) { + ucx_avl_free_content_node(al, node->left, destr); + ucx_avl_free_content_node(al, node->right, destr); + if (destr) { + destr(node->value); + } else { + alfree(al, node->value); + } + } +} + +void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) { + ucx_avl_free_content_node(tree->allocator, tree->root, destr); +} + UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) { UcxAVLNode *n = tree->root; int cmpresult; @@ -149,6 +168,46 @@ return n ? n->value : NULL; } +UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = tree->root; + UcxAVLNode *closest = NULL; + + intmax_t cmpresult; + intmax_t closest_dist; + closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX; + + while (n && (cmpresult = dfnc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + if (mode == UCX_AVL_FIND_CLOSEST) { + intmax_t dist = cmpresult; + if (dist < 0) dist *= -1; + if (dist < closest_dist) { + closest_dist = dist; + closest = n; + } + } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) { + if (cmpresult > closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) { + if (cmpresult < closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } + n = cmpresult > 0 ? n->right : n->left; + } + return n ? n : closest; +} + +void *ucx_avl_find(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode); + return n ? n->value : NULL; +} + int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) { return ucx_avl_put_s(tree, key, value, NULL); } @@ -272,3 +331,43 @@ size_t ucx_avl_count(UcxAVLTree *tree) { return ucx_avl_countn(tree->root); } + +UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) { + if (node->left) { + UcxAVLNode* n = node->left; + while (n->right) { + n = n->right; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->right == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} + +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) { + if (node->right) { + UcxAVLNode* n = node->right; + while (n->left) { + n = n->left; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->left == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/avl.h --- a/src/ucx/avl.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,249 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - - -/** - * @file avl.h - * - * AVL tree implementation. - * - * This binary search tree implementation allows average O(1) insertion and - * removal of elements (excluding binary search time). - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_AVL_H -#define UCX_AVL_H - -#include "ucx.h" -#include "allocator.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * UCX AVL Node type. - * - * @see UcxAVLNode - */ -typedef struct UcxAVLNode UcxAVLNode; - -/** - * UCX AVL Node. - */ -struct UcxAVLNode { - /** - * The key for this node. - */ - intptr_t key; - /** - * Data contained by this node. - */ - void *value; - /** - * The height of this (sub)-tree. - */ - size_t height; - /** - * Parent node. - */ - UcxAVLNode *parent; - /** - * Root node of left subtree. - */ - UcxAVLNode *left; - /** - * Root node of right subtree. - */ - UcxAVLNode *right; -}; - -/** - * UCX AVL Tree. - */ -typedef struct { - /** - * The UcxAllocator that shall be used to manage the memory for node data. - */ - UcxAllocator *allocator; - /** - * Root node of the tree. - */ - UcxAVLNode *root; - /** - * Compare function that shall be used to compare the UcxAVLNode keys. - * @see UcxAVLNode.key - */ - cmp_func cmpfunc; - /** - * Custom user data. - * This data will also be provided to the cmpfunc. - */ - void *userdata; -} UcxAVLTree; - -/** - * Initializes a new UcxAVLTree with a default allocator. - * - * @param cmpfunc the compare function that shall be used - * @return a new UcxAVLTree object - * @see ucx_avl_new_a() - */ -UcxAVLTree *ucx_avl_new(cmp_func cmpfunc); - -/** - * Initializes a new UcxAVLTree with the specified allocator. - * - * The cmpfunc should be capable of comparing two keys within this AVL tree. - * So if you want to use null terminated strings as keys, you could use the - * ucx_strcmp() function here. - * - * @param cmpfunc the compare function that shall be used - * @param allocator the UcxAllocator that shall be used - * @return a new UcxAVLTree object - */ -UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator); - -/** - * Destroys a UcxAVLTree. - * @param tree the tree to destroy - */ -void ucx_avl_free(UcxAVLTree *tree); - -/** - * Macro for initializing a new UcxAVLTree with the default allocator and a - * ucx_ptrcmp() compare function. - * - * @return a new default UcxAVLTree object - */ -#define ucx_avl_default_new() ucx_avl_new_a(ucx_ptrcmp, ucx_default_allocator()) - -/** - * Gets the node from the tree, that is associated with the specified key. - * @param tree the UcxAVLTree - * @param key the key - * @return the node (or NULL, if the key is not present) - */ -UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key); - -/** - * Gets the value from the tree, that is associated with the specified key. - * @param tree the UcxAVLTree - * @param key the key - * @return the value (or NULL, if the key is not present) - */ -void *ucx_avl_get(UcxAVLTree *tree, intptr_t key); - -/** - * Puts a key/value pair into the tree. - * - * Attention: use this function only, if a possible old value does not need - * to be preserved. - * - * @param tree the UcxAVLTree - * @param key the key - * @param value the new value - * @return zero, if and only if the operation succeeded - */ -int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value); - -/** - * Puts a key/value pair into the tree. - * - * This is a secure function which saves the old value to the variable pointed - * at by oldvalue. - * - * @param tree the UcxAVLTree - * @param key the key - * @param value the new value - * @param oldvalue optional: a pointer to the location where a possible old - * value shall be stored - * @return zero, if and only if the operation succeeded - */ -int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue); - -/** - * Removes a node from the AVL tree. - * - * Note: the specified node is logically removed. The tree implementation - * decides which memory area is freed. In most cases the here provided node - * is freed, so it's further use is generally undefined. - * - * @param tree the UcxAVLTree - * @param node the node to remove - * @return zero, if and only if an element has been removed - */ -int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node); - -/** - * Removes an element from the AVL tree. - * - * @param tree the UcxAVLTree - * @param key the key - * @return zero, if and only if an element has been removed - */ -int ucx_avl_remove(UcxAVLTree *tree, intptr_t key); - -/** - * Removes an element from the AVL tree. - * - * This is a secure function which saves the old key and value data from node - * to the variables at the location of oldkey and oldvalue (if specified), so - * they can be freed afterwards (if necessary). - * - * Note: the returned key in oldkey is possibly not the same as the provided - * key for the lookup (in terms of memory location). - * - * @param tree the UcxAVLTree - * @param key the key of the element to remove - * @param oldkey optional: a pointer to the location where the old key shall be - * stored - * @param oldvalue optional: a pointer to the location where the old value - * shall be stored - * @return zero, if and only if an element has been removed - */ -int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key, - intptr_t *oldkey, void **oldvalue); - -/** - * Counts the nodes in the specified UcxAVLTree. - * @param tree the AVL tree - * @return the node count - */ -size_t ucx_avl_count(UcxAVLTree *tree); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_AVL_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/buffer.c --- a/src/ucx/buffer.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/buffer.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,7 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "buffer.h" +#include "ucx/buffer.h" + #include #include #include @@ -64,8 +65,9 @@ UcxBuffer* ucx_buffer_extract( UcxBuffer *src, size_t start, size_t length, int flags) { - - if (src->size == 0 || length == 0 || start+length > src->capacity) { + if (src->size == 0 || length == 0 || + ((size_t)-1) - start < length || start+length > src->capacity) + { return NULL; } @@ -149,7 +151,10 @@ size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, UcxBuffer *buffer) { - size_t len = size * nitems; + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } size_t required = buffer->pos + len; if (buffer->pos > required) { return 0; @@ -183,7 +188,10 @@ size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, UcxBuffer *buffer) { - size_t len = size * nitems; + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } if (buffer->pos + len > buffer->size) { len = buffer->size - buffer->pos; if (size > 1) len -= len%size; @@ -223,12 +231,67 @@ if (ucx_buffer_eof(buffer)) { return EOF; } else { - int c = buffer->space[buffer->pos]; + int c = ((unsigned char*)buffer->space)[buffer->pos]; buffer->pos++; return c; } } -size_t ucx_buffer_puts(UcxBuffer *buffer, char *str) { +size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) { return ucx_buffer_write((const void*)str, 1, strlen(str), buffer); } + +int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) { + if (shift >= buffer->size) { + buffer->pos = buffer->size = 0; + } else { + memmove(buffer->space, buffer->space + shift, buffer->size - shift); + buffer->size -= shift; + + if (buffer->pos >= shift) { + buffer->pos -= shift; + } else { + buffer->pos = 0; + } + } + return 0; +} + +int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) { + size_t req_capacity = buffer->size + shift; + size_t movebytes; + + // auto extend buffer, if required and enabled + if (buffer->capacity < req_capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) { + return 1; + } + movebytes = buffer->size; + } else { + movebytes = buffer->capacity - shift; + } + } else { + movebytes = buffer->size; + } + + memmove(buffer->space + shift, buffer->space, movebytes); + buffer->size = shift+movebytes; + + buffer->pos += shift; + if (buffer->pos > buffer->size) { + buffer->pos = buffer->size; + } + + return 0; +} + +int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) { + if (shift < 0) { + return ucx_buffer_shift_left(buffer, (size_t) (-shift)); + } else if (shift > 0) { + return ucx_buffer_shift_right(buffer, (size_t) shift); + } else { + return 0; + } +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/buffer.h --- a/src/ucx/buffer.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file buffer.h - * - * Advanced buffer implementation. - * - * Instances of UcxBuffer can be used to read from or to write to like one - * would do with a stream. This allows the use of ucx_stream_copy() to copy - * contents from one buffer to another. - * - * Some features for convenient use of the buffer - * can be enabled. See the documentation of the macro constants for more - * information. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_BUFFER_H -#define UCX_BUFFER_H - -#include "ucx.h" -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * No buffer features enabled (all flags cleared). - */ -#define UCX_BUFFER_DEFAULT 0x00 - -/** - * If this flag is enabled, the buffer will automatically free its contents. - */ -#define UCX_BUFFER_AUTOFREE 0x01 - -/** - * If this flag is enabled, the buffer will automatically extends its capacity. - */ -#define UCX_BUFFER_AUTOEXTEND 0x02 - -/** UCX Buffer. */ -typedef struct { - /** A pointer to the buffer contents. */ - char *space; - /** Current position of the buffer. */ - size_t pos; - /** Current capacity (i.e. maximum size) of the buffer. */ - size_t capacity; - /** Current size of the buffer content. */ - size_t size; - /** - * Flag register for buffer features. - * @see #UCX_BUFFER_DEFAULT - * @see #UCX_BUFFER_AUTOFREE - * @see #UCX_BUFFER_AUTOEXTEND - */ - int flags; -} UcxBuffer; - -/** - * Creates a new buffer. - * - * Note: you may provide NULL as argument for - * space. Then this function will allocate the space and enforce - * the #UCX_BUFFER_AUTOFREE flag. - * - * @param space pointer to the memory area, or NULL to allocate - * new memory - * @param capacity the capacity of the buffer - * @param flags buffer features (see UcxBuffer.flags) - * @return the new buffer - */ -UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags); - -/** - * Destroys a buffer. - * - * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer - * are also freed. - * - * @param buffer the buffer to destroy - */ -void ucx_buffer_free(UcxBuffer* buffer); - -/** - * Creates a new buffer and fills it with extracted content from another buffer. - * - * Note: the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer. - * - * @param src the source buffer - * @param start the start position of extraction - * @param length the count of bytes to extract (must not be zero) - * @param flags feature mask for the new buffer - * @return a new buffer containing the extraction - */ -UcxBuffer* ucx_buffer_extract(UcxBuffer *src, - size_t start, size_t length, int flags); - -/** - * A shorthand macro for the full extraction of the buffer. - * - * @param src the source buffer - * @param flags feature mask for the new buffer - * @return a new buffer with the extracted content - */ -#define ucx_buffer_clone(src,flags) \ - ucx_buffer_extract(src, 0, (src)->capacity, flags) - -/** - * Moves the position of the buffer. - * - * The new position is relative to the whence argument. - * - * SEEK_SET marks the start of the buffer. - * SEEK_CUR marks the current position. - * SEEK_END marks the end of the buffer. - * - * With an offset of zero, this function sets the buffer position to zero - * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position - * unchanged (SEEK_CUR). - * - * @param buffer - * @param offset position offset relative to whence - * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END - * @return 0 on success, non-zero if the position is invalid - * - */ -int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence); - -/** - * Clears the buffer by resetting the position and deleting the data. - * - * The data is deleted by a zeroing it with call to memset(). - * - * @param buffer the buffer to be cleared - */ -#define ucx_buffer_clear(buffer) memset(buffer->space, 0, buffer->size); \ - buffer->size = 0; buffer->pos = 0; - -/** - * Tests, if the buffer position has exceeded the buffer capacity. - * - * @param buffer the buffer to test - * @return non-zero, if the current buffer position has exceeded the last - * available byte of the buffer. - */ -int ucx_buffer_eof(UcxBuffer *buffer); - - -/** - * Extends the capacity of the buffer. - * - * Note: The buffer capacity increased by a power of two. I.e. - * the buffer capacity is doubled, as long as it would not hold the current - * content plus the additional required bytes. - * - * Attention: the argument provided is the number of additional - * bytes the buffer shall hold. It is NOT the total number of bytes the - * buffer shall hold. - * - * @param buffer the buffer to extend - * @param additional_bytes the number of additional bytes the buffer shall - * at least hold - * @return 0 on success or a non-zero value on failure - */ -int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes); - -/** - * Writes data to a UcxBuffer. - * - * The position of the buffer is increased by the number of bytes written. - * - * @param ptr a pointer to the memory area containing the bytes to be written - * @param size the length of one element - * @param nitems the element count - * @param buffer the UcxBuffer to write to - * @return the total count of bytes written - */ -size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, - UcxBuffer *buffer); - -/** - * Reads data from a UcxBuffer. - * - * The position of the buffer is increased by the number of bytes read. - * - * @param ptr a pointer to the memory area where to store the read data - * @param size the length of one element - * @param nitems the element count - * @param buffer the UcxBuffer to read from - * @return the total number of elements read - */ -size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, - UcxBuffer *buffer); - -/** - * Writes a character to a buffer. - * - * The least significant byte of the argument is written to the buffer. If the - * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled, - * the buffer capacity is extended by ucx_buffer_extend(). If the feature is - * disabled or buffer extension fails, EOF is returned. - * - * On successful write the position of the buffer is increased. - * - * @param buffer the buffer to write to - * @param c the character to write as int value - * @return the byte that has bean written as int value or - * EOF when the end of the stream is reached and automatic - * extension is not enabled or not possible - */ -int ucx_buffer_putc(UcxBuffer *buffer, int c); - -/** - * Gets a character from a buffer. - * - * The current position of the buffer is increased after a successful read. - * - * @param buffer the buffer to read from - * @return the character as int value or EOF, if the - * end of the buffer is reached - */ -int ucx_buffer_getc(UcxBuffer *buffer); - -/** - * Writes a string to a buffer. - * - * @param buffer the buffer - * @param str the string - * @return the number of bytes written - */ -size_t ucx_buffer_puts(UcxBuffer *buffer, char *str); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_BUFFER_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/list.c --- a/src/ucx/list.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/list.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,13 +26,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "list.h" +#include "ucx/list.h" -UcxList *ucx_list_clone(UcxList *l, copy_func fnc, void *data) { +UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) { return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data); } -UcxList *ucx_list_clone_a(UcxAllocator *alloc, UcxList *l, +UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l, copy_func fnc, void *data) { UcxList *ret = NULL; while (l) { @@ -77,6 +77,7 @@ } void ucx_list_free_content(UcxList* list, ucx_destructor destr) { + if (!destr) destr = free; while (list != NULL) { destr(list->data); list = list->next; @@ -171,7 +172,8 @@ return (UcxList*)(index == 0 ? e : NULL); } -ssize_t ucx_list_find(UcxList *l, void *elem, cmp_func fnc, void *cmpdata) { +ssize_t ucx_list_find(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { ssize_t index = 0; UCX_FOREACH(e, l) { if (fnc) { @@ -188,7 +190,8 @@ return -1; } -int ucx_list_contains(UcxList *l, void *elem, cmp_func fnc, void *cmpdata) { +int ucx_list_contains(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { return ucx_list_find(l, elem, fnc, cmpdata) > -1; } @@ -205,15 +208,15 @@ return s; } -static UcxList *ucx_list_sort_merge(int length, - UcxList* restrict ls, UcxList* restrict le, UcxList* restrict re, +static UcxList *ucx_list_sort_merge(size_t length, + UcxList* ls, UcxList* le, UcxList* re, cmp_func fnc, void* data) { UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length); UcxList *rc, *lc; lc = ls; rc = le; - int n = 0; + size_t n = 0; while (lc && lc != le && rc != re) { if (fnc(lc->data, rc->data, data) <= 0) { sorted[n] = lc; @@ -254,9 +257,9 @@ } UcxList *lc; - int ln = 1; + size_t ln = 1; - UcxList *restrict ls = l, *restrict le, *restrict re; + UcxList *ls = l, *le, *re; // check how many elements are already sorted lc = ls; @@ -270,7 +273,7 @@ return l; // this list is already sorted :) } else { UcxList *rc; - int rn = 1; + size_t rn = 1; rc = le; // skip already sorted elements while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) { @@ -333,3 +336,93 @@ alfree(alloc, e); return l; } + + +static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata, + int op) { + + UcxList *res = NULL; + UcxList *cur = NULL; + const UcxList *src = left; + + do { + UCX_FOREACH(node, src) { + void* elem = node->data; + if ( + (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) || + (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) || + (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) { + UcxList *nl = almalloc(allocator, sizeof(UcxList)); + nl->prev = cur; + nl->next = NULL; + if (cpfnc) { + nl->data = cpfnc(elem, cpdata); + } else { + nl->data = elem; + } + if (cur != NULL) + cur->next = nl; + cur = nl; + if (res == NULL) + res = cur; + } + } + if (op == 0 && src == left) + src = right; + else + src = NULL; + } while (src != NULL); + + return res; +} + +UcxList* ucx_list_union(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_union_a(ucx_default_allocator(), + left, right, cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_union_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 0); +} + +UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_intersection_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 1); +} + +UcxList* ucx_list_difference(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_difference_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 2); +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/list.h --- a/src/ucx/list.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,393 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Doubly linked list implementation. - * - * @file list.h - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_LIST_H -#define UCX_LIST_H - -#include "ucx.h" -#include "allocator.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Loop statement for UCX lists. - * - * The first argument is the name of the iteration variable. The scope of - * this variable is limited to the UCX_FOREACH statement. - * - * The second argument is a pointer to the list. In most cases this will be the - * pointer to the first element of the list, but it may also be an arbitrary - * element of the list. The iteration will then start with that element. - * - * @param list The first element of the list - * @param elem The variable name of the element - */ -#define UCX_FOREACH(elem,list) \ - for (UcxList* elem = list ; elem != NULL ; elem = elem->next) - -/** - * UCX list type. - * @see UcxList - */ -typedef struct UcxList UcxList; - -/** - * UCX list structure. - */ -struct UcxList { - /** - * List element payload. - */ - void *data; - /** - * Pointer to the next list element or NULL, if this is the - * last element. - */ - UcxList *next; - /** - * Pointer to the previous list element or NULL, if this is - * the first element. - */ - UcxList *prev; -}; - -/** - * Creates an element-wise copy of a list. - * - * This function clones the specified list by creating new list elements and - * copying the data with the specified copy_func(). If no copy_func() is - * specified, a shallow copy is created and the new list will reference the - * same data as the source list. - * - * @param list the list to copy - * @param cpyfnc a pointer to the function that shall copy an element (may be - * NULL) - * @param data additional data for the copy_func() - * @return a pointer to the copy - */ -UcxList *ucx_list_clone(UcxList *list, copy_func cpyfnc, void* data); - -/** - * Creates an element-wise copy of a list using a UcxAllocator. - * - * See ucx_list_clone() for details. - * - * You might want to pass the allocator via the data parameter, - * to access it within the copy function for making deep copies. - * - * @param allocator the allocator to use - * @param list the list to copy - * @param cpyfnc a pointer to the function that shall copy an element (may be - * NULL) - * @param data additional data for the copy_func() - * @return a pointer to the copy - * @see ucx_list_clone() - */ -UcxList *ucx_list_clone_a(UcxAllocator *allocator, UcxList *list, - copy_func cpyfnc, void* data); - -/** - * Compares two UCX lists element-wise by using a compare function. - * - * Each element of the two specified lists are compared by using the specified - * compare function and the additional data. The type and content of this - * additional data depends on the cmp_func() used. - * - * If the list pointers denote elements within a list, the lists are compared - * starting with the denoted elements. Thus any previous elements are not taken - * into account. This might be useful to check, if certain list tails match - * each other. - * - * @param list1 the first list - * @param list2 the second list - * @param cmpfnc the compare function - * @param data additional data for the compare function - * @return 1, if and only if the two lists equal element-wise, 0 otherwise - */ -int ucx_list_equals(const UcxList *list1, const UcxList *list2, - cmp_func cmpfnc, void* data); - -/** - * Destroys the entire list. - * - * The members of the list are not automatically freed, so ensure they are - * otherwise referenced or destroyed by ucx_list_free_contents(). - * Otherwise, a memory leak is likely to occur. - * - * Caution: the argument MUST denote an entire list (i.e. a call - * to ucx_list_first() on the argument must return the argument itself) - * - * @param list the list to free - * @see ucx_list_free_contents() - */ -void ucx_list_free(UcxList *list); - -/** - * Destroys the entire list using a UcxAllocator. - * - * See ucx_list_free() for details. - * - * @param allocator the allocator to use - * @param list the list to free - * @see ucx_list_free() - */ -void ucx_list_free_a(UcxAllocator *allocator, UcxList *list); - -/** - * Destroys the contents of the specified list by calling the specified - * destructor on each of them. - * - * Note, that the contents are not usable afterwards and the list should be - * destroyed with ucx_list_free(). - * - * @param list the list for which the contents shall be freed - * @param destr the destructor function (e.g. stdlib free()) - * @see ucx_list_free() - */ -void ucx_list_free_content(UcxList* list, ucx_destructor destr); - - -/** - * Inserts an element at the end of the list. - * - * This is generally an O(n) operation, as the end of the list is retrieved with - * ucx_list_last(). - * - * @param list the list where to append the data, or NULL to - * create a new list - * @param data the data to insert - * @return list, if it is not NULL or a pointer to - * the newly created list otherwise - */ -UcxList *ucx_list_append(UcxList *list, void *data); - -/** - * Inserts an element at the end of the list using a UcxAllocator. - * - * See ucx_list_append() for details. - * - * @param allocator the allocator to use - * @param list the list where to append the data, or NULL to - * create a new list - * @param data the data to insert - * @return list, if it is not NULL or a pointer to - * the newly created list otherwise - * @see ucx_list_append() - */ -UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data); - -/** - * Inserts an element at the beginning of the list. - * - * You should overwrite the old list pointer by calling - * mylist = ucx_list_prepend(mylist, mydata);. However, you may - * also perform successive calls of ucx_list_prepend() on the same list pointer, - * as this function always searchs for the head of the list with - * ucx_list_first(). - * - * @param list the list where to insert the data or NULL to create - * a new list - * @param data the data to insert - * @return a pointer to the new list head - */ -UcxList *ucx_list_prepend(UcxList *list, void *data); - -/** - * Inserts an element at the beginning of the list using a UcxAllocator. - * - * See ucx_list_prepend() for details. - * - * @param allocator the allocator to use - * @param list the list where to insert the data or NULL to create - * a new list - * @param data the data to insert - * @return a pointer to the new list head - * @see ucx_list_prepend() - */ -UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data); - -/** - * Concatenates two lists. - * - * Either of the two arguments may be NULL. - * - * This function modifies the references to the next/previous element of - * the last/first element of list1/ - * list2. - * - * @param list1 first list - * @param list2 second list - * @return if list1 is NULL, list2 is - * returned, otherwise list1 is returned - */ -UcxList *ucx_list_concat(UcxList *list1, UcxList *list2); - -/** - * Returns the first element of a list. - * - * If the argument is the list pointer, it is directly returned. Otherwise - * this function traverses to the first element of the list and returns the - * list pointer. - * - * @param elem one element of the list - * @return the first element of the list, the specified element is a member of - */ -UcxList *ucx_list_first(const UcxList *elem); - -/** - * Returns the last element of a list. - * - * If the argument has no successor, it is the last element and therefore - * directly returned. Otherwise this function traverses to the last element of - * the list and returns it. - * - * @param elem one element of the list - * @return the last element of the list, the specified element is a member of - */ -UcxList *ucx_list_last(const UcxList *elem); - -/** - * Returns the list element at the specified index. - * - * @param list the list to retrieve the element from - * @param index index of the element to return - * @return the element at the specified index or NULL, if the - * index is greater than the list size - */ -UcxList *ucx_list_get(const UcxList *list, size_t index); - -/** - * Returns the index of an element. - * - * @param list the list where to search for the element - * @param elem the element to find - * @return the index of the element or -1 if the list does not contain the - * element - */ -ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem); - -/** - * Returns the element count of the list. - * - * @param list the list whose elements are counted - * @return the element count - */ -size_t ucx_list_size(const UcxList *list); - -/** - * Returns the index of an element containing the specified data. - * - * This function uses a cmp_func() to compare the data of each list element - * with the specified data. If no cmp_func is provided, the pointers are - * compared. - * - * If the list contains the data more than once, the index of the first - * occurrence is returned. - * - * @param list the list where to search for the data - * @param elem the element data - * @param cmpfnc the compare function - * @param data additional data for the compare function - * @return the index of the element containing the specified data or -1 if the - * data is not found in this list - */ -ssize_t ucx_list_find(UcxList *list, void *elem, cmp_func cmpfnc, void *data); - -/** - * Checks, if a list contains a specific element. - * - * An element is found, if ucx_list_find() returns a value greater than -1. - * - * @param list the list where to search for the data - * @param elem the element data - * @param cmpfnc the compare function - * @param data additional data for the compare function - * @return 1, if and only if the list contains the specified element data - * @see ucx_list_find() - */ -int ucx_list_contains(UcxList *list, void *elem, cmp_func cmpfnc, void *data); - -/** - * Sorts a UcxList with natural merge sort. - * - * This function uses O(n) additional temporary memory for merge operations - * that is automatically freed after each merge. - * - * As the head of the list might change, you MUST call this function - * as follows: mylist = ucx_list_sort(mylist, mycmpfnc, mydata);. - * - * @param list the list to sort - * @param cmpfnc the function that shall be used to compare the element data - * @param data additional data for the cmp_func() - * @return the sorted list - */ -UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data); - -/** - * Removes an element from the list. - * - * If the first element is removed, the list pointer changes. So it is - * highly recommended to always update the pointer by calling - * mylist = ucx_list_remove(mylist, myelem);. - * - * @param list the list from which the element shall be removed - * @param element the element to remove - * @return returns the updated list pointer or NULL, if the list - * is now empty - */ -UcxList *ucx_list_remove(UcxList *list, UcxList *element); - -/** - * Removes an element from the list using a UcxAllocator. - * - * See ucx_list_remove() for details. - * - * @param allocator the allocator to use - * @param list the list from which the element shall be removed - * @param element the element to remove - * @return returns the updated list pointer or NULL, if the list - * @see ucx_list_remove() - */ -UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list, - UcxList *element); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_LIST_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/logging.c --- a/src/ucx/logging.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/logging.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,7 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "logging.h" +#include "ucx/logging.h" + #include #include #include @@ -49,6 +50,8 @@ ucx_map_int_put(logger->levels, l, (void*) "[WARNING]"); l = UCX_LOGGER_INFO; ucx_map_int_put(logger->levels, l, (void*) "[INFO]"); + l = UCX_LOGGER_DEBUG; + ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]"); l = UCX_LOGGER_TRACE; ucx_map_int_put(logger->levels, l, (void*) "[TRACE]"); } @@ -68,12 +71,15 @@ const unsigned int line, const char *format, ...) { if (level <= logger->level) { char msg[UCX_LOGGER_MSGMAX]; - char *text; + const char *text; size_t k = 0; size_t n; if ((logger->mask & UCX_LOGGER_LEVEL) > 0) { - text = (char*) ucx_map_int_get(logger->levels, level); + text = (const char*) ucx_map_int_get(logger->levels, level); + if (!text) { + text = "[UNKNOWN]"; + } n = strlen(text); n = n > 256 ? 256 : n; memcpy(msg+k, text, n); @@ -85,13 +91,19 @@ k += strftime(msg+k, 128, logger->dateformat, localtime(&now)); } if ((logger->mask & UCX_LOGGER_SOURCE) > 0) { + char *fpart = strrchr(file, '/'); + if (fpart) file = fpart+1; + fpart = strrchr(file, '\\'); + if (fpart) file = fpart+1; n = strlen(file); memcpy(msg+k, file, n); k += n; k += sprintf(msg+k, ":%u ", line); } - msg[k++] = '-'; msg[k++] = ' '; + if (k > 0) { + msg[k++] = '-'; msg[k++] = ' '; + } va_list args; va_start (args, format); diff -r 21274e5950af -r a1f4cb076d2f src/ucx/logging.h --- a/src/ucx/logging.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Logging API. - * - * @file logging.h - * @author Mike Becker, Olaf Wintermann - */ -#ifndef UCX_LOGGING_H -#define UCX_LOGGING_H - -#include "ucx.h" -#include "map.h" -#include "string.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* leave enough space for custom log levels */ - -/** Log level for error messages. */ -#define UCX_LOGGER_ERROR 0x00 - -/** Log level for warning messages. */ -#define UCX_LOGGER_WARN 0x10 - -/** Log level for information messages. */ -#define UCX_LOGGER_INFO 0x20 - -/** Log level for debug messages. */ -#define UCX_LOGGER_DEBUG 0x30 - -/** Log level for trace messages. */ -#define UCX_LOGGER_TRACE 0x40 - -/** - * Output flag for the log level. - * If this flag is set, the log message will contain the log level. - * @see UcxLogger.mask - */ -#define UCX_LOGGER_LEVEL 0x01 - -/** - * Output flag for the timestmap. - * If this flag is set, the log message will contain the timestmap. - * @see UcxLogger.mask - */ -#define UCX_LOGGER_TIMESTAMP 0x02 - -/** - * Output flag for the source. - * If this flag is set, the log message will contain the source file and line - * number. - * @see UcxLogger.mask - */ -#define UCX_LOGGER_SOURCE 0x04 - -/** - * The UCX Logger object. - */ -typedef struct { - /** The stream this logger writes its messages to.*/ - void *stream; - - /** - * The write function that shall be used. - * For standard file or stdout loggers this might be standard fwrite - * (default). - */ - write_func writer; - - /** - * The date format for timestamp outputs including the delimiter - * (default: "%F %T %z "). - * @see UCX_LOGGER_TIMESTAMP - */ - char *dateformat; - - /** - * The level, this logger operates on. - * If a log command is issued, the message will only be logged, if the log - * level of the message is less or equal than the log level of the logger. - */ - unsigned int level; - - /** - * A configuration mask for automatic output. - * For each flag that is set, the logger automatically outputs some extra - * information like the timestamp or the source file and line number. - * See the documentation for the flags for details. - */ - unsigned int mask; - - /** - * A map of valid log levels for this logger. - * - * The keys represent all valid log levels and the values provide string - * representations, that are used, if the UCX_LOGGER_LEVEL flag is set. - * - * The exact data types are unsigned int for the key and - * const char* for the value. - * - * @see UCX_LOGGER_LEVEL - */ - UcxMap* levels; -} UcxLogger; - -/** - * Creates a new logger. - * @param stream the stream, which the logger shall write to - * @param level the level on which the logger shall operate - * @param mask configuration mask (cf. UcxLogger.mask) - * @return a new logger object - */ -UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask); - -/** - * Destroys the logger. - * - * The map containing the valid log levels is also automatically destroyed. - * - * @param logger the logger to destroy - */ -void ucx_logger_free(UcxLogger* logger); - -/** - * Internal log function - use macros instead. - * - * This function uses the format and variadic arguments for a - * printf()-style output of the log message. - * - * Dependent on the UcxLogger.mask some information is prepended. The complete - * format is: - * - * [LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message - * - * Attention: the message (including automatically generated information) - * is limited to 4096 characters. The level description is limited to - * 256 characters and the timestamp string is limited to 128 characters. - * - * @param logger the logger to use - * @param level the level to log on - * @param file information about the source file - * @param line information about the source line number - * @param format format string - * @param ... arguments - * @see ucx_logger_log() - */ -void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, - const unsigned int line, const char* format, ...); - -/** - * Logs a message at the specified level. - * @param logger the logger to use - * @param level the level to log the message on - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_log(logger, level, ...) \ - ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__) - -/** - * Shortcut for logging an error message. - * @param logger the logger to use - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_error(logger, ...) \ - ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__) - -/** - * Shortcut for logging an information message. - * @param logger the logger to use - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_info(logger, ...) \ - ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__) - -/** - * Shortcut for logging a warning message. - * @param logger the logger to use - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_warn(logger, ...) \ - ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__) - -/** - * Shortcut for logging a debug message. - * @param logger the logger to use - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_debug(logger, ...) \ - ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__) - -/** - * Shortcut for logging a trace message. - * @param logger the logger to use - * @param ... format string and arguments - * @see ucx_logger_logf() - */ -#define ucx_logger_trace(logger, ...) \ - ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__) - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_LOGGING_H */ diff -r 21274e5950af -r a1f4cb076d2f src/ucx/map.c --- a/src/ucx/map.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/map.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "ucx/map.h" + #include #include -#include "map.h" - UcxMap *ucx_map_new(size_t size) { return ucx_map_new_a(NULL, size); } @@ -86,7 +86,11 @@ UcxMapIterator iter = ucx_map_iterator(map); void *val; UCX_MAP_FOREACH(key, val, iter) { - destr(val); + if (destr) { + destr(val); + } else { + alfree(map->allocator, val); + } } } @@ -99,8 +103,7 @@ map->count = 0; } -int ucx_map_copy(UcxMap *restrict from, UcxMap *restrict to, - copy_func fnc, void *data) { +int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) { UcxMapIterator i = ucx_map_iterator(from); void *value; UCX_MAP_FOREACH(key, value, i) { @@ -111,9 +114,14 @@ return 0; } -UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data) { +UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) { + return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data); +} + +UcxMap *ucx_map_clone_a(UcxAllocator *allocator, + UcxMap const *map, copy_func fnc, void *data) { size_t bs = (map->count * 5) >> 1; - UcxMap *newmap = ucx_map_new(bs > map->size ? bs : map->size); + UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size); if (!newmap) { return NULL; } @@ -151,19 +159,22 @@ UcxAllocator *allocator = map->allocator; if (key.hash == 0) { - key.hash = ucx_hash((char*)key.data, key.len); + key.hash = ucx_hash((const char*)key.data, key.len); } + + struct UcxMapKey mapkey; + mapkey.hash = key.hash; - size_t slot = key.hash%map->size; - UcxMapElement *restrict elm = map->map[slot]; - UcxMapElement *restrict prev = NULL; + size_t slot = mapkey.hash%map->size; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *prev = NULL; - while (elm && elm->key.hash < key.hash) { + while (elm && elm->key.hash < mapkey.hash) { prev = elm; elm = elm->next; } - if (!elm || elm->key.hash != key.hash) { + if (!elm || elm->key.hash != mapkey.hash) { UcxMapElement *e = (UcxMapElement*)almalloc( allocator, sizeof(UcxMapElement)); if (!e) { @@ -185,8 +196,9 @@ return -1; } memcpy(kd, key.data, key.len); - key.data = kd; - elm->key = key; + mapkey.data = kd; + mapkey.len = key.len; + elm->key = mapkey; map->count++; } elm->data = data; @@ -194,14 +206,14 @@ return 0; } -void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, _Bool remove) { +static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) { if(key.hash == 0) { - key.hash = ucx_hash((char*)key.data, key.len); + key.hash = ucx_hash((const char*)key.data, key.len); } size_t slot = key.hash%map->size; - UcxMapElement *restrict elm = map->map[slot]; - UcxMapElement *restrict pelm = NULL; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *pelm = NULL; while (elm && elm->key.hash <= key.hash) { if(elm->key.hash == key.hash) { int n = (key.len > elm->key.len) ? elm->key.len : key.len; @@ -228,19 +240,19 @@ return NULL; } -void *ucx_map_get(UcxMap *map, UcxKey key) { - return ucx_map_get_and_remove(map, key, 0); +void *ucx_map_get(UcxMap const *map, UcxKey key) { + return ucx_map_get_and_remove((UcxMap *)map, key, 0); } void *ucx_map_remove(UcxMap *map, UcxKey key) { return ucx_map_get_and_remove(map, key, 1); } -UcxKey ucx_key(void *data, size_t len) { +UcxKey ucx_key(const void *data, size_t len) { UcxKey key; key.data = data; key.len = len; - key.hash = ucx_hash((const char*) data, len); + key.hash = ucx_hash((const char*)data, len); return key; } @@ -287,7 +299,7 @@ return h; } -UcxMapIterator ucx_map_iterator(UcxMap *map) { +UcxMapIterator ucx_map_iterator(UcxMap const *map) { UcxMapIterator i; i.map = map; i.cur = NULL; @@ -309,7 +321,9 @@ if (e->data) { i->cur = e; *elm = e->data; - *key = e->key; + key->data = e->key.data; + key->hash = e->key.hash; + key->len = e->key.len; return 1; } @@ -326,3 +340,63 @@ return 0; } +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_union_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata); + ucx_map_copy(second, result, cpfnc, cpdata); + return result; +} + +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_intersection_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ? + first->size : second->size); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + return result; +} + +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_difference_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + + UcxMap *result = ucx_map_new_a(allocator, first->size - second->count); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (!ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + ucx_map_rehash(result); + return result; +} \ No newline at end of file diff -r 21274e5950af -r a1f4cb076d2f src/ucx/map.h --- a/src/ucx/map.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,423 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file map.h - * - * Hash map implementation. - * - * This implementation uses murmur hash 2 and separate chaining with linked - * lists. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_MAP_H -#define UCX_MAP_H - -#include "ucx.h" -#include "string.h" -#include "allocator.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Loop statement for UCX maps. - * - * The key variable is implicitly defined, but the - * value variable must be already declared as type information - * cannot be inferred. - * - * @param key the variable name for the key - * @param value the variable name for the value - * @param iter a UcxMapIterator - * @see ucx_map_iterator() - */ -#define UCX_MAP_FOREACH(key,value,iter) \ - for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);) - -/** Type for the UCX map. @see UcxMap */ -typedef struct UcxMap UcxMap; - -/** Type for a key of a UcxMap. @see UcxKey */ -typedef struct UcxKey UcxKey; - -/** Type for an element of a UcxMap. @see UcxMapElement */ -typedef struct UcxMapElement UcxMapElement; - -/** Type for an iterator over a UcxMap. @see UcxMapIterator */ -typedef struct UcxMapIterator UcxMapIterator; - -/** Structure for the UCX map. */ -struct UcxMap { - /** An allocator that is used for the map elements. */ - UcxAllocator *allocator; - /** The array of map element lists. */ - UcxMapElement **map; - /** The size of the map is the length of the element list array. */ - size_t size; - /** The count of elements currently stored in this map. */ - size_t count; -}; - -/** Structure for a key of a UcxMap. */ -struct UcxKey { - /** The key data. */ - void *data; - /** The length of the key data. */ - size_t len; - /** The hash value of the key data. */ - int hash; -}; - -/** Structure for an element of a UcxMap. */ -struct UcxMapElement { - /** The value data. */ - void *data; - - /** A pointer to the next element in the current list. */ - UcxMapElement *next; - - /** The corresponding key. */ - UcxKey key; -}; - -/** Structure for an iterator over a UcxMap. */ -struct UcxMapIterator { - /** The map to iterate over. */ - UcxMap *map; - - /** The current map element. */ - UcxMapElement *cur; - - /** - * The current index of the element list array. - * Attention: this is NOT the element index! Do NOT - * manually iterate over the map by increasing this index. Use - * ucx_map_iter_next(). - * @see UcxMap.map*/ - size_t index; -}; - -/** - * Creates a new hash map with the specified size. - * @param size the size of the hash map - * @return a pointer to the new hash map - */ -UcxMap *ucx_map_new(size_t size); - -/** - * Creates a new hash map with the specified size using a UcxAllocator. - * @param allocator the allocator to use - * @param size the size of the hash map - * @return a pointer to the new hash map - */ -UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size); - -/** - * Frees a hash map. - * - * Note: the contents are not freed, use ucx_map_free_content() - * before calling this function to achieve that. - * - * @param map the map to be freed - * @see ucx_map_free_content() - */ -void ucx_map_free(UcxMap *map); - -/** - * Frees the contents of a hash map. - * - * This is a convenience function that iterates over the map and passes all - * values to the specified destructor function (e.g. stdlib free()). - * - * You must ensure, that it is valid to pass each value in the map to the same - * destructor function. - * - * You should free or clear the map afterwards, as the contents will be invalid. - * - * @param map for which the contents shall be freed - * @param destr pointer to the destructor function - * @see ucx_map_free() - * @see ucx_map_clear() - */ -void ucx_map_free_content(UcxMap *map, ucx_destructor destr); - -/** - * Clears a hash map. - * - * Note: the contents are not freed, use ucx_map_free_content() - * before calling this function to achieve that. - * - * @param map the map to be cleared - * @see ucx_map_free_content() - */ -void ucx_map_clear(UcxMap *map); - - -/** - * Copies contents from a map to another map using a copy function. - * - * Note: The destination map does not need to be empty. However, if it - * contains data with keys that are also present in the source map, the contents - * are overwritten. - * - * @param from the source map - * @param to the destination map - * @param fnc the copy function or NULL if the pointer address - * shall be copied - * @param data additional data for the copy function - * @return 0 on success or a non-zero value on memory allocation errors - */ -int ucx_map_copy(UcxMap *restrict from, UcxMap *restrict to, - copy_func fnc, void *data); - -/** - * Clones the map and rehashes if necessary. - * - * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. - * This function always ensures a new UcxMap.size of at least - * 2.5*UcxMap.count. - * - * @param map the map to clone - * @param fnc the copy function to use or NULL if the new and - * the old map shall share the data pointers - * @param data additional data for the copy function - * @return the cloned map - * @see ucx_map_copy() - */ -UcxMap *ucx_map_clone(UcxMap *map, copy_func fnc, void *data); - -/** - * Increases size of the hash map, if necessary. - * - * The load value is 0.75*UcxMap.size. If the element count exceeds the load - * value, the map needs to be rehashed. Otherwise no action is performed and - * this function simply returns 0. - * - * The rehashing process ensures, that the UcxMap.size is at least - * 2.5*UcxMap.count. So there is enough room for additional elements without - * the need of another soon rehashing. - * - * You can use this function to dramatically increase access performance. - * - * @param map the map to rehash - * @return 1, if a memory allocation error occurred, 0 otherwise - */ -int ucx_map_rehash(UcxMap *map); - -/** - * Puts a key/value-pair into the map. - * - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - */ -int ucx_map_put(UcxMap *map, UcxKey key, void *value); - -/** - * Retrieves a value by using a key. - * - * @param map the map - * @param key the key - * @return the value - */ -void* ucx_map_get(UcxMap *map, UcxKey key); - -/** - * Removes a key/value-pair from the map by using the key. - * - * @param map the map - * @param key the key - * @return the removed value - */ -void* ucx_map_remove(UcxMap *map, UcxKey key); - -/** - * Shorthand for putting data with a sstr_t key into the map. - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - * @see ucx_map_put() - */ -#define ucx_map_sstr_put(map, key, value) \ - ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value) - -/** - * Shorthand for putting data with a C string key into the map. - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - * @see ucx_map_put() - */ -#define ucx_map_cstr_put(map, key, value) \ - ucx_map_put(map, ucx_key((void*)key, strlen(key)), (void*)value) - -/** - * Shorthand for putting data with an integer key into the map. - * @param map the map - * @param key the key - * @param value the value - * @return 0 on success, non-zero value on failure - * @see ucx_map_put() - */ -#define ucx_map_int_put(map, key, value) \ - ucx_map_put(map, ucx_key((void*)&key, sizeof(key)), (void*)value) - -/** - * Shorthand for getting data from the map with a sstr_t key. - * @param map the map - * @param key the key - * @return the value - * @see ucx_map_get() - */ -#define ucx_map_sstr_get(map, key) \ - ucx_map_get(map, ucx_key(key.ptr, key.length)) - -/** - * Shorthand for getting data from the map with a C string key. - * @param map the map - * @param key the key - * @return the value - * @see ucx_map_get() - */ -#define ucx_map_cstr_get(map, key) \ - ucx_map_get(map, ucx_key((void*)key, strlen(key))) - -/** - * Shorthand for getting data from the map with an integer key. - * @param map the map - * @param key the key - * @return the value - * @see ucx_map_get() - */ -#define ucx_map_int_get(map, key) \ - ucx_map_get(map, ucx_key((void*)&key, sizeof(int))) - -/** - * Shorthand for removing data from the map with a sstr_t key. - * @param map the map - * @param key the key - * @return the removed value - * @see ucx_map_remove() - */ -#define ucx_map_sstr_remove(map, key) \ - ucx_map_remove(map, ucx_key(key.ptr, key.length)) - -/** - * Shorthand for removing data from the map with a C string key. - * @param map the map - * @param key the key - * @return the removed value - * @see ucx_map_remove() - */ -#define ucx_map_cstr_remove(map, key) \ - ucx_map_remove(map, ucx_key((void*)key, strlen(key))) - -/** - * Shorthand for removing data from the map with an integer key. - * @param map the map - * @param key the key - * @return the removed value - * @see ucx_map_remove() - */ -#define ucx_map_int_remove(map, key) \ - ucx_map_remove(map, ucx_key((void*)&key, sizeof(key))) - -/** - * Creates a UcxKey based on the given data. - * - * This function implicitly computes the hash. - * - * @param data the data for the key - * @param len the length of the data - * @return a UcxKey with implicitly computed hash - * @see ucx_hash() - */ -UcxKey ucx_key(void *data, size_t len); - -/** - * Computes a murmur hash-2. - * - * @param data the data to hash - * @param len the length of the data - * @return the murmur hash-2 of the data - */ -int ucx_hash(const char *data, size_t len); - -/** - * Creates an iterator for a map. - * - * Note: A UcxMapIterator iterates over all elements in all element - * lists successively. Therefore the order highly depends on the key hashes and - * may vary under different map sizes. So generally you may NOT rely on - * the iteration order. - * - * Note: The iterator is NOT initialized. You need to call - * ucx_map_iter_next() at least once before accessing any information. However, - * it is not recommended to access the fields of a UcxMapIterator directly. - * - * @param map the map to create the iterator for - * @return an iterator initialized on the first element of the - * first element list - * @see ucx_map_iter_next() - */ -UcxMapIterator ucx_map_iterator(UcxMap *map); - -/** - * Proceeds to the next element of the map (if any). - * - * Subsequent calls on the same iterator proceed to the next element and - * store the key/value-pair into the memory specified as arguments of this - * function. - * - * If no further elements are found, this function returns zero and leaves the - * last found key/value-pair in memory. - * - * @param iterator the iterator to use - * @param key a pointer to the memory where to store the key - * @param value a pointer to the memory where to store the value - * @return 1, if another element was found, 0 if all elements has been processed - * @see ucx_map_iterator() - */ -int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value); - - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_MAP_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/mempool.c --- a/src/ucx/mempool.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/mempool.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,6 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "ucx/mempool.h" + #include #include #include @@ -34,8 +36,6 @@ #endif #include -#include "mempool.h" - /** Capsule for destructible memory chunks. */ typedef struct { /** The destructor for the memory chunk. */ @@ -56,18 +56,26 @@ void *ptr; } ucx_regdestr; -UCX_EXTERN void ucx_mempool_shared_destr(void* ptr) { +#ifdef __cplusplus +extern "C" +#endif +void ucx_mempool_shared_destr(void* ptr) { ucx_regdestr *rd = (ucx_regdestr*)ptr; rd->destructor(rd->ptr); } UcxMempool *ucx_mempool_new(size_t n) { + size_t poolsz; + if(ucx_szmul(n, sizeof(void*), &poolsz)) { + return NULL; + } + UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool)); if (!pool) { return NULL; } - pool->data = (void**) malloc(n * sizeof(void*)); + pool->data = (void**) malloc(poolsz); if (pool->data == NULL) { free(pool); return NULL; @@ -93,25 +101,39 @@ } int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) { - void **data = (void**) realloc(pool->data, newcap*sizeof(void*)); + if (newcap < pool->ndata) { + return 1; + } + + size_t newcapsz; + if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) { + return 1; + } + + void **data = (void**) realloc(pool->data, newcapsz); if (data) { pool->data = data; pool->size = newcap; - return EXIT_SUCCESS; + return 0; } else { - return EXIT_FAILURE; + return 1; } } void *ucx_mempool_malloc(UcxMempool *pool, size_t n) { + if(((size_t)-1) - sizeof(ucx_destructor) < n) { + return NULL; + } + if (pool->ndata >= pool->size) { - // The hard coded 16 is documented for this function and ucx_mempool_new - if (ucx_mempool_chcap(pool, pool->size + 16) == EXIT_FAILURE) { + size_t newcap = pool->size*2; + if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) { return NULL; } } - ucx_memchunk *mem = (ucx_memchunk*)malloc(sizeof(ucx_destructor) + n); + void *p = malloc(sizeof(ucx_destructor) + n); + ucx_memchunk *mem = (ucx_memchunk*)p; if (!mem) { return NULL; } @@ -124,7 +146,12 @@ } void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) { - void *ptr = ucx_mempool_malloc(pool, nelem*elsize); + size_t msz; + if(ucx_szmul(nelem, elsize, &msz)) { + return NULL; + } + + void *ptr = ucx_mempool_malloc(pool, msz); if (!ptr) { return NULL; } @@ -133,6 +160,10 @@ } void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) { + if(((size_t)-1) - sizeof(ucx_destructor) < n) { + return NULL; + } + char *mem = ((char*)ptr) - sizeof(ucx_destructor); char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor)); if (!newm) { @@ -147,7 +178,7 @@ } fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", (intptr_t)ptr, (intptr_t)pool); - exit(EXIT_FAILURE); + abort(); } else { return newm + sizeof(ucx_destructor); } @@ -172,7 +203,7 @@ } fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", (intptr_t)ptr, (intptr_t)pool); - exit(EXIT_FAILURE); + abort(); } void ucx_mempool_destroy(UcxMempool *pool) { diff -r 21274e5950af -r a1f4cb076d2f src/ucx/mempool.h --- a/src/ucx/mempool.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file mempool.h - * - * Memory pool implementation. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_MEMPOOL_H -#define UCX_MEMPOOL_H - -#include "ucx.h" -#include -#include "allocator.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * UCX mempool structure. - */ -typedef struct { - /** UcxAllocator based on this pool */ - UcxAllocator *allocator; - - /** List of pointers to pooled memory. */ - void **data; - - /** Count of pooled memory items. */ - size_t ndata; - - /** Memory pool size. */ - size_t size; -} UcxMempool; - -/** Shorthand for a new default memory pool with a capacity of 16 elements. */ -#define ucx_mempool_new_default() ucx_mempool_new(16) - - -/** - * Creates a memory pool with the specified initial size. - * - * As the created memory pool automatically grows in size by 16 elements, when - * trying to allocate memory on a full pool, it is recommended that you use - * a multiple of 16 for the initial size. - * - * @param n initial pool size (should be a multiple of 16) - * @return a pointer to the new memory pool - */ -UcxMempool *ucx_mempool_new(size_t n); - -/** - * Resizes a memory pool. - * - * @param pool the pool to resize - * @param newcap the new capacity - * @return EXIT_SUCCESS on success or - * EXIT_FAILURE on failure - */ -int ucx_mempool_chcap(UcxMempool *pool, size_t newcap); - -/** - * Changes the pool size to the next smallest multiple of 16. - * - * You may use this macro, to reduce the pool size after freeing - * many pooled memory items. - * - * @param pool the pool to clamp - * @return EXIT_SUCCESS on success or - * EXIT_FAILURE on failure - */ -#define ucx_mempool_clamp(pool) ucx_mempool_chcap(pool, \ - (pool->ndata & ~0xF)+0x10) - -/** - * Allocates pooled memory. - * - * @param pool the memory pool - * @param n amount of memory to allocate - * @return a pointer to the allocated memory - * @see ucx_allocator_malloc() - */ -void *ucx_mempool_malloc(UcxMempool *pool, size_t n); -/** - * Allocates a pooled memory array. - * - * The content of the allocated memory is set to zero. - * - * @param pool the memory pool - * @param nelem amount of elements to allocate - * @param elsize amount of memory per element - * @return a pointer to the allocated memory - * @see ucx_allocator_calloc() - */ -void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize); - -/** - * Reallocates pooled memory. - * - * If the memory to be reallocated is not contained by the specified pool, the - * behavior is undefined. - * - * @param pool the memory pool - * @param ptr a pointer to the memory that shall be reallocated - * @param n the new size of the memory - * @return a pointer to the new location of the memory - * @see ucx_allocator_realloc() - */ -void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n); - -/** - * Frees pooled memory. - * - * Before freeing the memory, the specified destructor function (if any) - * is called. - * - * If you specify memory, that is not pooled by the specified memory pool, the - * behavior is undefined. - * - * @param pool the memory pool - * @param ptr a pointer to the memory that shall be freed - * @see ucx_mempool_set_destr() - */ -void ucx_mempool_free(UcxMempool *pool, void *ptr); - -/** - * Destroys a memory pool. - * - * For each element the destructor function (if any) is called and the element - * is freed. - * - * Each of the registered destructor function that has no corresponding element - * within the pool (namely those registered by ucx_mempool_reg_destr) is - * called interleaving with the element destruction, but with guarantee to the - * order in which they were registered (FIFO order). - * - * - * @param pool the mempool to destroy - */ -void ucx_mempool_destroy(UcxMempool *pool); - -/** - * Sets a destructor function for the specified memory. - * - * The destructor is automatically called when the memory is freed or the - * pool is destroyed. - * - * The only requirement for the specified memory is, that it MUST be - * pooled memory by a UcxMempool or an element-compatible mempool. The pointer - * to the destructor function is saved in a reserved area before the actual - * memory. - * - * @param ptr pooled memory - * @param func a pointer to the destructor function - * @see ucx_mempool_free() - * @see ucx_mempool_destroy() - */ -void ucx_mempool_set_destr(void *ptr, ucx_destructor func); - -/** - * Registers a destructor function for the specified (non-pooled) memory. - * - * This is useful, if you have memory that has not been allocated by a mempool, - * but shall be managed by a mempool. - * - * This function creates an entry in the specified mempool and the memory will - * therefore (logically) convert to pooled memory. - * - * @param pool the memory pool - * @param ptr data the destructor is registered for - * @param destr a pointer to the destructor function - */ -void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_MEMPOOL_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/properties.c --- a/src/ucx/properties.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/properties.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,12 +26,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "ucx/properties.h" + #include #include #include -#include "properties.h" - UcxProperties *ucx_properties_new() { UcxProperties *parser = (UcxProperties*)malloc( sizeof(UcxProperties)); diff -r 21274e5950af -r a1f4cb076d2f src/ucx/properties.h --- a/src/ucx/properties.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * @file properties.h - * - * Load / store utilities for properties files. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_PROPERTIES_H -#define UCX_PROPERTIES_H - -#include "ucx.h" -#include "map.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * UcxProperties object for parsing properties data. - * Most of the fields are for internal use only. You may configure the - * properties parser, e.g. by changing the used delimiter or specifying - * up to three different characters that shall introduce comments. - */ -typedef struct { - /** - * Input buffer (don't set manually). - * Automatically set by calls to ucx_properties_fill(). - */ - char *buffer; - - /** - * Length of the input buffer (don't set manually). - * Automatically set by calls to ucx_properties_fill(). - */ - size_t buflen; - - /** - * Current buffer position (don't set manually). - * Used by ucx_properties_next(). - */ - size_t pos; - - /** - * Internal temporary buffer (don't set manually). - * Used by ucx_properties_next(). - */ - char *tmp; - - /** - * Internal temporary buffer length (don't set manually). - * Used by ucx_properties_next(). - */ - size_t tmplen; - - /** - * Internal temporary buffer capacity (don't set manually). - * Used by ucx_properties_next(). - */ - size_t tmpcap; - - /** - * Parser error code. - * This is always 0 on success and a nonzero value on syntax errors. - * The value is set by ucx_properties_next(). - */ - int error; - - /** - * The delimiter that shall be used. - * This is '=' by default. - */ - char delimiter; - - /** - * The first comment character. - * This is '#' by default. - */ - char comment1; - - /** - * The second comment character. - * This is not set by default. - */ - char comment2; - - /** - * The third comment character. - * This is not set by default. - */ - char comment3; -} UcxProperties; - - -/** - * Constructs a new UcxProperties object. - * @return a pointer to the new UcxProperties object - */ -UcxProperties *ucx_properties_new(); - -/** - * Destroys a UcxProperties object. - * @param prop the UcxProperties object to destroy - */ -void ucx_properties_free(UcxProperties *prop); - -/** - * Sets the input buffer for the properties parser. - * - * After calling this function, you may parse the data by calling - * ucx_properties_next() until it returns 0. The function ucx_properties2map() - * is a convenience function that reads as much data as possible by using this - * function. - * - * - * @param prop the UcxProperties object - * @param buf a pointer to the new buffer - * @param len the payload length of the buffer - * @see ucx_properties_next() - * @see ucx_properties2map() - */ -void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); - -/** - * Retrieves the next key/value-pair. - * - * This function returns a nonzero value as long as there are key/value-pairs - * found. If no more key/value-pairs are found, you may refill the input buffer - * with ucx_properties_fill(). - * - * Attention: the sstr_t.ptr pointers of the output parameters point to - * memory within the input buffer of the parser and will get invalid some time. - * If you want long term copies of the key/value-pairs, use sstrdup() after - * calling this function. - * - * @param prop the UcxProperties object - * @param name a pointer to the sstr_t that shall contain the property name - * @param value a pointer to the sstr_t that shall contain the property value - * @return Nonzero, if a key/value-pair was successfully retrieved - * @see ucx_properties_fill() - */ -int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value); - -/** - * Retrieves all available key/value-pairs and puts them into a UcxMap. - * - * This is done by successive calls to ucx_properties_next() until no more - * key/value-pairs can be retrieved. - * - * @param prop the UcxProperties object - * @param map the target map - * @return The UcxProperties.error code (i.e. 0 on success). - * @see ucx_properties_fill() - */ -int ucx_properties2map(UcxProperties *prop, UcxMap *map); - -/** - * Loads a properties file to a UcxMap. - * - * This is a convenience function that reads data from an input - * stream until the end of the stream is reached. - * - * @param map the map object to write the key/value-pairs to - * @param file the FILE* stream to read from - * @return 0 on success, or a non-zero value on error - * - * @see ucx_properties_fill() - * @see ucx_properties2map() - */ -int ucx_properties_load(UcxMap *map, FILE *file); - -/** - * Stores a UcxMap to a file. - * - * The key/value-pairs are written by using the following format: - * - * [key] = [value]\\n - * - * @param map the map to store - * @param file the FILE* stream to write to - * @return 0 on success, or a non-zero value on error - */ -int ucx_properties_store(UcxMap *map, FILE *file); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_PROPERTIES_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/stack.c --- a/src/ucx/stack.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/stack.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,7 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "stack.h" +#include "ucx/stack.h" + #include static size_t ucx_stack_align(size_t n) { @@ -119,13 +120,15 @@ return; } - size_t len = ucx_stack_topsize(stack); - if (len > n) { - len = n; + if (dest) { + size_t len = ucx_stack_topsize(stack); + if (len > n) { + len = n; + } + + memcpy(dest, stack->top, len); } - memcpy(dest, stack->top, len); - ucx_stack_free(stack, stack->top); } @@ -141,3 +144,22 @@ return 0; } } + +void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) { + void *space = ucx_stack_malloc(stack, n); + if (space) { + memcpy(space, data, n); + } + return space; +} + +void *ucx_stack_pusharr(UcxStack *stack, + size_t nelem, size_t elsize, const void *data) { + + // skip the memset by using malloc + void *space = ucx_stack_malloc(stack, nelem*elsize); + if (space) { + memcpy(space, data, nelem*elsize); + } + return space; +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/stack.h --- a/src/ucx/stack.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file stack.h - * - * Default stack memory allocation implementation. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_STACK_H -#define UCX_STACK_H - -#include "ucx.h" -#include "allocator.h" - -#ifdef __cplusplus -extern "C" { -#endif - - -/** - * UCX stack structure. - */ -typedef struct { - /** UcxAllocator based on this stack */ - UcxAllocator allocator; - - /** Stack size. */ - size_t size; - - /** Pointer to the bottom of the stack */ - char *space; - - /** Pointer to the top of the stack */ - char *top; -} UcxStack; - -/** - * Metadata for each UCX stack element. - */ -struct ucx_stack_metadata { - /** - * Location of the previous element (NULL if this is the first) - */ - char *prev; - - /** Size of this element */ - size_t size; -}; - -/** - * Initializes UcxStack structure with memory. - * - * @param stack a pointer to an uninitialized stack structure - * @param space the memory area that shall be managed - * @param size size of the memory area - * @return a new UcxStack structure - */ -void ucx_stack_init(UcxStack *stack, char* space, size_t size); - -/** - * Allocates stack memory. - * - * @param stack a pointer to the stack - * @param n amount of memory to allocate - * @return a pointer to the allocated memory - * @see ucx_allocator_malloc() - */ -void *ucx_stack_malloc(UcxStack *stack, size_t n); - -/** - * Alias for #ucx_stack_malloc(). - * @param stack a pointer to the stack - * @param n amount of memory to allocate - * @return a pointer to the allocated memory - * @see ucx_stack_malloc - */ -#define ucx_stack_push(stack, n) ucx_stack_malloc(stack, n) - -/** - * Allocates an array of stack memory - * - * The content of the allocated memory is set to zero. - * - * @param stack a pointer to the stack - * @param nelem amount of elements to allocate - * @param elsize amount of memory per element - * @return a pointer to the allocated memory - * @see ucx_allocator_calloc() - */ -void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize); - -/** - * Alias for #ucx_stack_calloc(). - * - * @param stack a pointer to the stack - * @param n amount of elements to allocate - * @param elsize amount of memory per element - * @return a pointer to the allocated memory - * @see ucx_stack_calloc - */ -#define ucx_stack_pusharr(stack,n,elsize) ucx_stack_calloc(stack,n,elssize) - -/** - * Reallocates memory on the stack. - * - * Shrinking memory is always safe. Extending memory can be very expensive. - * - * @param stack the stack - * @param ptr a pointer to the memory that shall be reallocated - * @param n the new size of the memory - * @return a pointer to the new location of the memory - * @see ucx_allocator_realloc() - */ -void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n); - -/** - * Frees memory on the stack. - * - * Freeing stack memory behaves in a special way. - * - * If the element, that should be freed, is the top most element of the stack, - * it is removed from the stack. Otherwise it is marked as freed. Marked - * elements are removed, when they become the top most elements of the stack. - * - * @param stack a pointer to the stack - * @param ptr a pointer to the memory that shall be freed - */ -void ucx_stack_free(UcxStack *stack, void *ptr); - - -/** - * Returns the size of the top most element. - * @param stack a pointer to the stack - * @return the size of the top most element - */ -#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\ - (stack)->top - 1)->size : 0) - -/** - * Removes the top most element from the stack and copies the content to - * dest, if specified. - * - * Use #ucx_stack_topsize()# to get the amount of memory that must be available - * at the location of dest. - * - * @param stack a pointer to the stack - * @param dest the location where the contents shall be written to, or - * NULL, if the element shall only be removed. - * @see ucx_stack_free - * @see ucx_stack_popn - */ -#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1) - -/** - * Removes the top most element from the stack and copies the content to - * dest. - * - * In contrast to #ucx_stack_pop() the dest pointer MUST - * NOT be NULL. - * - * @param stack a pointer to the stack - * @param dest the location where the contents shall be written to - * @param n copies at most n elements to dest - * @see ucx_stack_pop - */ -void ucx_stack_popn(UcxStack *stack, void *dest, size_t n); - -/** - * Returns the remaining available memory on the specified stack. - * - * @param stack a pointer to the stack - * @return the remaining available memory - */ -size_t ucx_stack_avail(UcxStack *stack); - -/** - * Checks, if the stack is empty. - * - * @param stack a pointer to the stack - * @return nonzero, if the stack is empty, zero otherwise - */ -#define ucx_stack_empty(stack) (!(stack)->top) - -/** - * Computes a recommended size for the stack memory area. Note, that - * reallocations have not been taken into account, so you might need to reserve - * twice as much memory to allow many reallocations. - * - * @param size the approximate payload - * @param elems the approximate count of element allocations - * @return a recommended size for the stack space based on the information - * provided - */ -#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \ - (elems + 1)) - - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_STACK_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/string.c --- a/src/ucx/string.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/string.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,13 +26,19 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "ucx/string.h" + +#include "ucx/allocator.h" + #include #include #include +#include #include -#include "string.h" -#include "allocator.h" +#ifndef _WIN32 +#include /* for strncasecmp() */ +#endif /* _WIN32 */ sstr_t sstr(char *cstring) { sstr_t string; @@ -48,13 +54,35 @@ return string; } -size_t sstrnlen(size_t n, sstr_t s, ...) { +scstr_t scstr(const char *cstring) { + scstr_t string; + string.ptr = cstring; + string.length = strlen(cstring); + return string; +} + +scstr_t scstrn(const char *cstring, size_t length) { + scstr_t string; + string.ptr = cstring; + string.length = length; + return string; +} + + +size_t scstrnlen(size_t n, ...) { + if (n == 0) return 0; + va_list ap; - size_t size = s.length; - va_start(ap, s); + va_start(ap, n); + + size_t size = 0; - for (size_t i = 1 ; i < n ; i++) { - sstr_t str = va_arg(ap, sstr_t); + for (size_t i = 0 ; i < n ; i++) { + scstr_t str = va_arg(ap, scstr_t); + if(SIZE_MAX - str.length < size) { + size = SIZE_MAX; + break; + } size += str.length; } va_end(ap); @@ -65,8 +93,7 @@ static sstr_t sstrvcat_a( UcxAllocator *a, size_t count, - sstr_t s1, - sstr_t s2, + scstr_t s1, va_list ap) { sstr_t str; str.ptr = NULL; @@ -75,7 +102,13 @@ return str; } - sstr_t *strings = (sstr_t*) calloc(count, sizeof(sstr_t)); + scstr_t s2 = va_arg (ap, scstr_t); + + if(((size_t)-1) - s1.length < s2.length) { + return str; + } + + scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t)); if(!strings) { return str; } @@ -83,16 +116,25 @@ // get all args and overall length strings[0] = s1; strings[1] = s2; - size_t strlen = s1.length + s2.length; + size_t slen = s1.length + s2.length; + int error = 0; for (size_t i=2;i str_length) { + return 0; + } + + if(length > str_length - start) { + length = str_length - start; + } + *newlen = length; + *newpos = start; + return 1; } sstr_t sstrsubs(sstr_t s, size_t start) { @@ -135,132 +199,301 @@ } sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) { - sstr_t new_sstr; - if (start >= s.length) { - new_sstr.ptr = NULL; - new_sstr.length = 0; - } else { - if (length > s.length-start) { - length = s.length-start; + size_t pos; + sstr_t ret = { NULL, 0 }; + if(ucx_substring(s.length, start, length, &ret.length, &pos)) { + ret.ptr = s.ptr + pos; + } + return ret; +} + +scstr_t scstrsubs(scstr_t string, size_t start) { + return scstrsubsl(string, start, string.length-start); +} + +scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) { + size_t pos; + scstr_t ret = { NULL, 0 }; + if(ucx_substring(s.length, start, length, &ret.length, &pos)) { + ret.ptr = s.ptr + pos; + } + return ret; +} + + +static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) { + for(size_t i=0;i 0) { + for(size_t i=length ; i>0 ; i--) { + if(str[i-1] == chr) { + *pos = i-1; + return 1; + } + } + } + return 0; } sstr_t sstrchr(sstr_t s, int c) { - for(size_t i=0;i 0) { - for(size_t i=s.length;i>0;i--) { - if(s.ptr[i-1] == c) { - return sstrsubs(s, i-1); - } - } + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return sstrsubs(s, pos); } - sstr_t n; - n.ptr = NULL; - n.length = 0; - return n; + return sstrn(NULL, 0); +} + +scstr_t scstrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); } -sstr_t sstrstr(sstr_t string, sstr_t match) { - if (match.length == 0) { - return string; +scstr_t scstrrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); +} + +#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \ + ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index]) + +#define ptable_w(useheap, ptable, index, src) do {\ + if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\ + else ((size_t*)ptable)[index] = src;\ + } while (0); + + +static const char* ucx_strstr( + const char *str, + size_t length, + const char *match, + size_t matchlen, + size_t *newlen) +{ + *newlen = length; + if (matchlen == 0) { + return str; } - for (size_t i = 0 ; i < string.length ; i++) { - sstr_t substr = sstrsubs(string, i); - if (sstrprefix(substr, match)) { - return substr; + const char *result = NULL; + size_t resultlen = 0; + + /* + * IMPORTANT: + * our prefix table contains the prefix length PLUS ONE + * this is our decision, because we want to use the full range of size_t + * the original algorithm needs a (-1) at one single place + * and we want to avoid that + */ + + /* static prefix table */ + static uint8_t s_prefix_table[256]; + + /* check pattern length and use appropriate prefix table */ + /* if the pattern exceeds static prefix table, allocate on the heap */ + register int useheap = matchlen > 255; + register void* ptable = useheap ? + calloc(matchlen+1, sizeof(size_t)): s_prefix_table; + + /* keep counter in registers */ + register size_t i, j; + + /* fill prefix table */ + i = 0; j = 0; + ptable_w(useheap, ptable, i, j); + while (i < matchlen) { + while (j >= 1 && match[j-1] != match[i]) { + ptable_r(j, useheap, ptable, j-1); } + i++; j++; + ptable_w(useheap, ptable, i, j); + } + + /* search */ + i = 0; j = 1; + while (i < length) { + while (j >= 1 && str[i] != match[j-1]) { + ptable_r(j, useheap, ptable, j-1); + } + i++; j++; + if (j-1 == matchlen) { + size_t start = i - matchlen; + result = str + start; + resultlen = length - start; + break; + } + } + + /* if prefix table was allocated on the heap, free it */ + if (ptable != s_prefix_table) { + free(ptable); } - sstr_t emptystr; - emptystr.length = 0; - emptystr.ptr = NULL; - return emptystr; + *newlen = resultlen; + return result; +} + +sstr_t scstrsstr(sstr_t string, scstr_t match) { + sstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; } -sstr_t* sstrsplit(sstr_t s, sstr_t d, ssize_t *n) { - return sstrsplit_a(ucx_default_allocator(), s, d, n); +scstr_t scstrscstr(scstr_t string, scstr_t match) { + scstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; } -sstr_t* sstrsplit_a(UcxAllocator *allocator, sstr_t s, sstr_t d, ssize_t *n) { +#undef ptable_r +#undef ptable_w + +sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) { + return scstrsplit_a(ucx_default_allocator(), s, d, n); +} + +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) { if (s.length == 0 || d.length == 0) { *n = -1; return NULL; } - - sstr_t* result; - ssize_t nmax = *n; - *n = 1; - - /* special case: exact match - no processing needed */ - if (sstrcmp(s, d) == 0) { - *n = 0; - return NULL; + + /* special cases: delimiter is at least as large as the string */ + if (d.length >= s.length) { + /* exact match */ + if (sstrcmp(s, d) == 0) { + *n = 0; + return NULL; + } else /* no match possible */ { + *n = 1; + sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t)); + if(result) { + *result = sstrdup_a(allocator, s); + } else { + *n = -2; + } + return result; + } } - sstr_t sv = sstrdup(s); - if (sv.length == 0) { - *n = -2; - return NULL; - } - - for (size_t i = 0 ; i < s.length ; i++) { - sstr_t substr = sstrsubs(sv, i); - if (sstrprefix(substr, d)) { - (*n)++; - for (size_t j = 0 ; j < d.length ; j++) { - sv.ptr[i+j] = 0; - } - i += d.length - 1; // -1, because the loop will do a i++ - } - if ((*n) == nmax) break; - } - result = (sstr_t*) almalloc(allocator, sizeof(sstr_t)*(*n)); + + ssize_t nmax = *n; + size_t arrlen = 16; + sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t)); if (result) { - char *pptr = sv.ptr; - for (ssize_t i = 0 ; i < *n ; i++) { - size_t l = strlen(pptr); - char* ptr = (char*) almalloc(allocator, l + 1); - if (ptr) { - memcpy(ptr, pptr, l); - ptr[l] = 0; + scstr_t curpos = s; + ssize_t j = 1; + while (1) { + scstr_t match; + /* optimize for one byte delimiters */ + if (d.length == 1) { + match = curpos; + for (size_t i = 0 ; i < curpos.length ; i++) { + if (curpos.ptr[i] == *(d.ptr)) { + match.ptr = curpos.ptr + i; + break; + } + match.length--; + } + } else { + match = scstrscstr(curpos, d); + } + if (match.length > 0) { + /* is this our last try? */ + if (nmax == 0 || j < nmax) { + /* copy the current string to the array */ + scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr); + result[j-1] = sstrdup_a(allocator, item); + size_t processed = item.length + d.length; + curpos.ptr += processed; + curpos.length -= processed; - result[i] = sstrn(ptr, l); - pptr += l + d.length; + /* allocate memory for the next string */ + j++; + if (j > arrlen) { + arrlen *= 2; + size_t reallocsz; + sstr_t* reallocated = NULL; + if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) { + reallocated = (sstr_t*) alrealloc( + allocator, result, reallocsz); + } + if (reallocated) { + result = reallocated; + } else { + for (ssize_t i = 0 ; i < j-1 ; i++) { + alfree(allocator, result[i].ptr); + } + alfree(allocator, result); + *n = -2; + return NULL; + } + } + } else { + /* nmax reached, copy the _full_ remaining string */ + result[j-1] = sstrdup_a(allocator, curpos); + break; + } } else { - for (ssize_t j = i-1 ; j >= 0 ; j--) { - alfree(allocator, result[j].ptr); - } - alfree(allocator, result); - *n = -2; + /* no more matches, copy last string */ + result[j-1] = sstrdup_a(allocator, curpos); break; } } + *n = j; } else { *n = -2; } - - free(sv.ptr); return result; } -int sstrcmp(sstr_t s1, sstr_t s2) { +int scstrcmp(scstr_t s1, scstr_t s2) { if (s1.length == s2.length) { return memcmp(s1.ptr, s2.ptr, s1.length); } else if (s1.length > s2.length) { @@ -270,7 +503,7 @@ } } -int sstrcasecmp(sstr_t s1, sstr_t s2) { +int scstrcasecmp(scstr_t s1, scstr_t s2) { if (s1.length == s2.length) { #ifdef _WIN32 return _strnicmp(s1.ptr, s2.ptr, s1.length); @@ -284,11 +517,11 @@ } } -sstr_t sstrdup(sstr_t s) { +sstr_t scstrdup(scstr_t s) { return sstrdup_a(ucx_default_allocator(), s); } -sstr_t sstrdup_a(UcxAllocator *allocator, sstr_t s) { +sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) { sstr_t newstring; newstring.ptr = (char*)almalloc(allocator, s.length + 1); if (newstring.ptr) { @@ -303,21 +536,38 @@ return newstring; } -sstr_t sstrtrim(sstr_t string) { - sstr_t newstr = string; + +static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) { + const char *newptr = s; + size_t length = len; - while (newstr.length > 0 && isspace(*newstr.ptr)) { - newstr.ptr++; - newstr.length--; + while(length > 0 && isspace(*newptr)) { + newptr++; + length--; } - while (newstr.length > 0 && isspace(newstr.ptr[newstr.length-1])) { - newstr.length--; + while(length > 0 && isspace(newptr[length-1])) { + length--; } + *newlen = length; + return newptr - s; +} + +sstr_t sstrtrim(sstr_t string) { + sstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); return newstr; } -int sstrprefix(sstr_t string, sstr_t prefix) { +scstr_t scstrtrim(scstr_t string) { + scstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); + return newstr; +} + +int scstrprefix(scstr_t string, scstr_t prefix) { if (string.length == 0) { return prefix.length == 0; } @@ -332,7 +582,7 @@ } } -int sstrsuffix(sstr_t string, sstr_t suffix) { +int scstrsuffix(scstr_t string, scstr_t suffix) { if (string.length == 0) { return suffix.length == 0; } @@ -348,7 +598,39 @@ } } -sstr_t sstrlower(sstr_t string) { +int scstrcaseprefix(scstr_t string, scstr_t prefix) { + if (string.length == 0) { + return prefix.length == 0; + } + if (prefix.length == 0) { + return 1; + } + + if (prefix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubsl(string, 0, prefix.length); + return scstrcasecmp(subs, prefix) == 0; + } +} + +int scstrcasesuffix(scstr_t string, scstr_t suffix) { + if (string.length == 0) { + return suffix.length == 0; + } + if (suffix.length == 0) { + return 1; + } + + if (suffix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubs(string, string.length-suffix.length); + return scstrcasecmp(subs, suffix) == 0; + } +} + +sstr_t scstrlower(scstr_t string) { sstr_t ret = sstrdup(string); for (size_t i = 0; i < ret.length ; i++) { ret.ptr[i] = tolower(ret.ptr[i]); @@ -356,7 +638,7 @@ return ret; } -sstr_t sstrlower_a(UcxAllocator *allocator, sstr_t string) { +sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) { sstr_t ret = sstrdup_a(allocator, string); for (size_t i = 0; i < ret.length ; i++) { ret.ptr[i] = tolower(ret.ptr[i]); @@ -364,7 +646,7 @@ return ret; } -sstr_t sstrupper(sstr_t string) { +sstr_t scstrupper(scstr_t string) { sstr_t ret = sstrdup(string); for (size_t i = 0; i < ret.length ; i++) { ret.ptr[i] = toupper(ret.ptr[i]); @@ -372,10 +654,154 @@ return ret; } -sstr_t sstrupper_a(UcxAllocator *allocator, sstr_t string) { +sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) { sstr_t ret = sstrdup_a(allocator, string); for (size_t i = 0; i < ret.length ; i++) { ret.ptr[i] = toupper(ret.ptr[i]); } return ret; } + +#define REPLACE_INDEX_BUFFER_MAX 100 + +struct scstrreplace_ibuf { + size_t* buf; + unsigned int len; /* small indices */ + struct scstrreplace_ibuf* next; +}; + +static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) { + while (buf) { + struct scstrreplace_ibuf *next = buf->next; + free(buf->buf); + free(buf); + buf = next; + } +} + +sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, + scstr_t pattern, scstr_t replacement, size_t replmax) { + + if (pattern.length == 0 || pattern.length > str.length || replmax == 0) + return sstrdup(str); + + /* Compute expected buffer length */ + size_t ibufmax = str.length / pattern.length; + size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; + if (ibuflen > REPLACE_INDEX_BUFFER_MAX) { + ibuflen = REPLACE_INDEX_BUFFER_MAX; + } + + /* Allocate first index buffer */ + struct scstrreplace_ibuf *firstbuf, *curbuf; + firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!firstbuf) return sstrn(NULL, 0); + firstbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!firstbuf->buf) { + free(firstbuf); + return sstrn(NULL, 0); + } + + /* Search occurrences */ + scstr_t searchstr = str; + size_t found = 0; + do { + scstr_t match = scstrscstr(searchstr, pattern); + if (match.length > 0) { + /* Allocate next buffer in chain, if required */ + if (curbuf->len == ibuflen) { + struct scstrreplace_ibuf *nextbuf = + calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!nextbuf) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + nextbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!nextbuf->buf) { + free(nextbuf); + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + curbuf->next = nextbuf; + curbuf = nextbuf; + } + + /* Record match index */ + found++; + size_t idx = match.ptr - str.ptr; + curbuf->buf[curbuf->len++] = idx; + searchstr.ptr = match.ptr + pattern.length; + searchstr.length = str.length - idx - pattern.length; + } else { + break; + } + } while (searchstr.length > 0 && found < replmax); + + /* Allocate result string */ + sstr_t result; + { + ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; + size_t rcount = 0; + curbuf = firstbuf; + do { + rcount += curbuf->len; + curbuf = curbuf->next; + } while (curbuf); + result.length = str.length + rcount * adjlen; + result.ptr = almalloc(allocator, result.length); + if (!result.ptr) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + } + + /* Build result string */ + curbuf = firstbuf; + size_t srcidx = 0; + char* destptr = result.ptr; + do { + for (size_t i = 0; i < curbuf->len; i++) { + /* Copy source part up to next match*/ + size_t idx = curbuf->buf[i]; + size_t srclen = idx - srcidx; + if (srclen > 0) { + memcpy(destptr, str.ptr+srcidx, srclen); + destptr += srclen; + srcidx += srclen; + } + + /* Copy the replacement and skip the source pattern */ + srcidx += pattern.length; + memcpy(destptr, replacement.ptr, replacement.length); + destptr += replacement.length; + } + curbuf = curbuf->next; + } while (curbuf); + memcpy(destptr, str.ptr+srcidx, str.length-srcidx); + + /* Free index buffer */ + scstrrepl_free_ibuf(firstbuf); + + return result; +} + +sstr_t scstrreplacen(scstr_t str, scstr_t pattern, + scstr_t replacement, size_t replmax) { + return scstrreplacen_a(ucx_default_allocator(), + str, pattern, replacement, replmax); +} + + +// type adjustment functions +scstr_t ucx_sc2sc(scstr_t str) { + return str; +} +scstr_t ucx_ss2sc(sstr_t str) { + scstr_t cs; + cs.ptr = str.ptr; + cs.length = str.length; + return cs; +} +scstr_t ucx_ss2c_s(scstr_t c) { + return c; +} diff -r 21274e5950af -r a1f4cb076d2f src/ucx/string.h --- a/src/ucx/string.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,457 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Bounded string implementation. - * - * The UCX strings (sstr_t) provide an alternative to C strings. - * The main difference to C strings is, that sstr_t does not - * need to be NULL-terminated. Instead the length is stored - * within the structure. - * - * When using sstr_t, developers must be full aware of what type - * of string (NULL-terminated) or not) they are using, when - * accessing the char* ptr directly. - * - * The UCX string module provides some common string functions, known from - * standard libc, working with sstr_t. - * - * @file string.h - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_STRING_H -#define UCX_STRING_H - -#include "ucx.h" -#include "allocator.h" -#include - -/** Shortcut for a sstr_t struct literal. */ -#define ST(s) { (char*)s, sizeof(s)-1 } - -/** Shortcut for the conversion of a C string to a sstr_t. */ -#define S(s) sstrn((char*)s, sizeof(s)-1) - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The UCX string structure. - */ -typedef struct { - /** A reference to the string (not necessarily NULL - * -terminated) */ - char *ptr; - /** The length of the string */ - size_t length; -} sstr_t; - -/** - * Creates a new sstr_t based on a C string. - * - * The length is implicitly inferred by using a call to strlen(). - * - * Note: the sstr_t will hold a reference to the C string. If you - * do want a copy, use sstrdup() on the return value of this function. - * - * @param cstring the C string to wrap - * @return a new sstr_t containing the C string - * - * @see sstrn() - */ -sstr_t sstr(char *cstring); - -/** - * Creates a new sstr_t of the specified length based on a C string. - * - * Note: the sstr_t will hold a reference to the C string. If you - * do want a copy, use sstrdup() on the return value of this function. - * - * @param cstring the C string to wrap - * @param length the length of the string - * @return a new sstr_t containing the C string - * - * @see sstr() - * @see S() - */ -sstr_t sstrn(char *cstring, size_t length); - - -/** - * Returns the cumulated length of all specified strings. - * - * At least one string must be specified. - * - * Attention: if the count argument does not match the count of the - * specified strings, the behavior is undefined. - * - * @param count the total number of specified strings (so at least 1) - * @param string the first string - * @param ... all other strings - * @return the cumulated length of all strings - */ -size_t sstrnlen(size_t count, sstr_t string, ...); - -/** - * Concatenates two or more strings. - * - * The resulting string will be allocated by standard malloc(). - * So developers MUST pass the sstr_t.ptr to free(). - * - * The sstr_t.ptr of the return value will always be NULL- - * terminated. - * - * @param count the total number of strings to concatenate - * @param s1 first string - * @param s2 second string - * @param ... all remaining strings - * @return the concatenated string - */ -sstr_t sstrcat(size_t count, sstr_t s1, sstr_t s2, ...); - -/** - * Concatenates two or more strings using a UcxAllocator. - * - * See sstrcat() for details. - * - * @param a the allocator to use - * @param count the total number of strings to concatenate - * @param s1 first string - * @param s2 second string - * @param ... all remaining strings - * @return the concatenated string - */ -sstr_t sstrcat_a(UcxAllocator *a, size_t count, sstr_t s1, sstr_t s2, ...); - - -/** - * Returns a substring starting at the specified location. - * - * Attention: the new string references the same memory area as the - * input string and will NOT be NULL-terminated. - * Use sstrdup() to get a copy. - * - * @param string input string - * @param start start location of the substring - * @return a substring of string starting at start - * - * @see sstrsubsl() - * @see sstrchr() - */ -sstr_t sstrsubs(sstr_t string, size_t start); - -/** - * Returns a substring with a maximum length starting at the specified location. - * - * Attention: the new string references the same memory area as the - * input string and will NOT be NULL-terminated. - * Use sstrdup() to get a copy. - * - * @param string input string - * @param start start location of the substring - * @param length the maximum length of the substring - * @return a substring of string starting at start - * with a maximum length of length - * - * @see sstrsubs() - * @see sstrchr() - */ -sstr_t sstrsubsl(sstr_t string, size_t start, size_t length); - -/** - * Returns a substring starting at the location of the first occurrence of the - * specified character. - * - * If the string does not contain the character, an empty string is returned. - * - * @param string the string where to locate the character - * @param chr the character to locate - * @return a substring starting at the first location of chr - * - * @see sstrsubs() - */ -sstr_t sstrchr(sstr_t string, int chr); - -/** - * Returns a substring starting at the location of the last occurrence of the - * specified character. - * - * If the string does not contain the character, an empty string is returned. - * - * @param string the string where to locate the character - * @param chr the character to locate - * @return a substring starting at the last location of chr - * - * @see sstrsubs() - */ -sstr_t sstrrchr(sstr_t string, int chr); - -/** - * Returns a substring starting at the location of the first occurrence of the - * specified string. - * - * If the string does not contain the other string, an empty string is returned. - * - * If match is an empty string, the complete string is - * returned. - * - * @param string the string to be scanned - * @param match string containing the sequence of characters to match - * @return a substring starting at the first occurrence of - * match, or an empty string, if the sequence is not - * present in string - */ -sstr_t sstrstr(sstr_t string, sstr_t match); - -/** - * Splits a string into parts by using a delimiter string. - * - * This function will return NULL, if one of the following happens: - *
    - *
  • the string length is zero
  • - *
  • the delimeter length is zero
  • - *
  • the string equals the delimeter
  • - *
  • memory allocation fails
  • - *
- * - * The integer referenced by count is used as input and determines - * the maximum size of the resulting array, i.e. the maximum count of splits to - * perform + 1. - * - * The integer referenced by count is also used as output and is - * set to - *
    - *
  • -2, on memory allocation errors
  • - *
  • -1, if either the string or the delimiter is an empty string
  • - *
  • 0, if the string equals the delimiter
  • - *
  • 1, if the string does not contain the delimiter
  • - *
  • the count of array items, otherwise
  • - *
- * - * If the string starts with the delimiter, the first item of the resulting - * array will be an empty string. - * - * If the string ends with the delimiter and the maximum list size is not - * exceeded, the last array item will be an empty string. - * - * Attention: The array pointer AND all sstr_t.ptr of the array - * items must be manually passed to free(). Use sstrsplit_a() with - * an allocator to managed memory, to avoid this. - * - * @param string the string to split - * @param delim the delimiter string - * @param count IN: the maximum size of the resulting array (0 = no limit), - * OUT: the actual size of the array - * @return a sstr_t array containing the split strings or - * NULL on error - * - * @see sstrsplit_a() - */ -sstr_t* sstrsplit(sstr_t string, sstr_t delim, ssize_t *count); - -/** - * Performing sstrsplit() using a UcxAllocator. - * - * Read the description of sstrsplit() for details. - * - * The memory for the sstr_t.ptr pointers of the array items and the memory for - * the sstr_t array itself are allocated by using the UcxAllocator.malloc() - * function. - * - * Note: the allocator is not used for memory that is freed within the - * same call of this function (locally scoped variables). - * - * @param allocator the UcxAllocator used for allocating memory - * @param string the string to split - * @param delim the delimiter string - * @param count IN: the maximum size of the resulting array (0 = no limit), - * OUT: the actual size of the array - * @return a sstr_t array containing the split strings or - * NULL on error - * - * @see sstrsplit() - */ -sstr_t* sstrsplit_a(UcxAllocator *allocator, sstr_t string, sstr_t delim, - ssize_t *count); - -/** - * Compares two UCX strings with standard memcmp(). - * - * At first it compares the sstr_t.length attribute of the two strings. The - * memcmp() function is called, if and only if the lengths match. - * - * @param s1 the first string - * @param s2 the second string - * @return -1, if the length of s1 is less than the length of s2 or 1, if the - * length of s1 is greater than the length of s2 or the result of - * memcmp() otherwise (i.e. 0 if the strings match) - */ -int sstrcmp(sstr_t s1, sstr_t s2); - -/** - * Compares two UCX strings ignoring the case. - * - * At first it compares the sstr_t.length attribute of the two strings. If and - * only if the lengths match, both strings are compared char by char ignoring - * the case. - * - * @param s1 the first string - * @param s2 the second string - * @return -1, if the length of s1 is less than the length of s2 or 1, if the - * length of s1 is greater than the length of s2 or the difference between the - * first two differing characters otherwise (i.e. 0 if the strings match and - * no characters differ) - */ -int sstrcasecmp(sstr_t s1, sstr_t s2); - -/** - * Creates a duplicate of the specified string. - * - * The new sstr_t will contain a copy allocated by standard - * malloc(). So developers MUST pass the sstr_t.ptr to - * free(). - * - * The sstr_t.ptr of the return value will always be NULL- - * terminated. - * - * @param string the string to duplicate - * @return a duplicate of the string - * @see sstrdup_a() - */ -sstr_t sstrdup(sstr_t string); - -/** - * Creates a duplicate of the specified string using a UcxAllocator. - * - * The new sstr_t will contain a copy allocated by the allocators - * ucx_allocator_malloc function. So it is implementation depended, whether the - * returned sstr_t.ptr pointer must be passed to the allocators - * ucx_allocator_free function manually. - * - * The sstr_t.ptr of the return value will always be NULL- - * terminated. - * - * @param allocator a valid instance of a UcxAllocator - * @param string the string to duplicate - * @return a duplicate of the string - * @see sstrdup() - */ -sstr_t sstrdup_a(UcxAllocator *allocator, sstr_t string); - -/** - * Omits leading and trailing spaces. - * - * This function returns a new sstr_t containing a trimmed version of the - * specified string. - * - * Note: the new sstr_t references the same memory, thus you - * MUST NOT pass the sstr_t.ptr of the return value to - * free(). It is also highly recommended to avoid assignments like - * mystr = sstrtrim(mystr); as you lose the reference to the - * source string. Assignments of this type are only permitted, if the - * sstr_t.ptr of the source string does not need to be freed or if another - * reference to the source string exists. - * - * @param string the string that shall be trimmed - * @return a new sstr_t containing the trimmed string - */ -sstr_t sstrtrim(sstr_t string); - -/** - * Checks, if a string has a specific prefix. - * @param string the string to check - * @param prefix the prefix the string should have - * @return 1, if and only if the string has the specified prefix, 0 otherwise - */ -int sstrprefix(sstr_t string, sstr_t prefix); - -/** - * Checks, if a string has a specific suffix. - * @param string the string to check - * @param suffix the suffix the string should have - * @return 1, if and only if the string has the specified suffix, 0 otherwise - */ -int sstrsuffix(sstr_t string, sstr_t suffix); - -/** - * Returns a lower case version of a string. - * - * This function creates a duplicate of the input string, first. See the - * documentation of sstrdup() for the implications. - * - * @param string the input string - * @return the resulting lower case string - * @see sstrdup() - */ -sstr_t sstrlower(sstr_t string); - -/** - * Returns a lower case version of a string. - * - * This function creates a duplicate of the input string, first. See the - * documentation of sstrdup_a() for the implications. - * - * @param allocator the allocator used for duplicating the string - * @param string the input string - * @return the resulting lower case string - * @see sstrdup_a() - */ -sstr_t sstrlower_a(UcxAllocator *allocator, sstr_t string); - -/** - * Returns a upper case version of a string. - * - * This function creates a duplicate of the input string, first. See the - * documentation of sstrdup() for the implications. - * - * @param string the input string - * @return the resulting upper case string - * @see sstrdup() - */ -sstr_t sstrupper(sstr_t string); - -/** - * Returns a upper case version of a string. - * - * This function creates a duplicate of the input string, first. See the - * documentation of sstrdup_a() for the implications. - * - * @param allocator the allocator used for duplicating the string - * @param string the input string - * @return the resulting upper case string - * @see sstrdup_a() - */ -sstr_t sstrupper_a(UcxAllocator *allocator, sstr_t string); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_STRING_H */ diff -r 21274e5950af -r a1f4cb076d2f src/ucx/test.c --- a/src/ucx/test.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/test.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "test.h" +#include "ucx/test.h" UcxTestSuite* ucx_test_suite_new() { UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite)); @@ -86,6 +86,6 @@ elem->test(suite, output); } fwrite("\nAll test completed.\n", 1, 21, output); - fprintf(output, " Total: %d\n Success: %d\n Failure: %d\n", + fprintf(output, " Total: %u\n Success: %u\n Failure: %u\n", suite->success+suite->failure, suite->success, suite->failure); } diff -r 21274e5950af -r a1f4cb076d2f src/ucx/test.h --- a/src/ucx/test.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file: test.h - * - * UCX Test Framework. - * - * Usage of this test framework: - * - * **** IN HEADER FILE: **** - * - *
- * UCX_TEST(function_name)
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) // optional
- * 
- * - * **** IN SOURCE FILE: **** - *
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
- *   // tests with UCX_TEST_ASSERT()
- * }
- * 
- * UCX_TEST(function_name) {
- *   // memory allocation and other stuff here
- *   #UCX_TEST_BEGIN
- *   // tests with UCX_TEST_ASSERT() and/or
- *   // calls with UCX_TEST_CALL_SUBROUTINE() here
- *   #UCX_TEST_END
- *   // cleanup of memory here
- * }
- * 
- * - * Note: if a test fails, a longjump is performed - * back to the #UCX_TEST_BEGIN macro! - * - * Attention: Do not call own functions within a test, that use - * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE(). - * - * - * @author Mike Becker - * @author Olaf Wintermann - * - */ - -#ifndef UCX_TEST_H -#define UCX_TEST_H - -#include "ucx.h" -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef __FUNCTION__ - -/** - * Alias for the __func__ preprocessor macro. - * Some compilers use __func__ and others use __FUNCTION__. - * We use __FUNCTION__ so we define it for those compilers which use - * __func__. - */ -#define __FUNCTION__ __func__ -#endif - -/** Type for the UcxTestSuite. */ -typedef struct UcxTestSuite UcxTestSuite; - -/** Pointer to a test function. */ -typedef void(*UcxTest)(UcxTestSuite*,FILE*); - -/** Type for the internal list of test cases. */ -typedef struct UcxTestList UcxTestList; - -/** Structure for the internal list of test cases. */ -struct UcxTestList { - - /** Test case. */ - UcxTest test; - - /** Pointer to the next list element. */ - UcxTestList *next; -}; - -/** - * A test suite containing multiple test cases. - */ -struct UcxTestSuite { - - /** The number of successful tests after the suite has been run. */ - unsigned int success; - - /** The number of failed tests after the suite has been run. */ - unsigned int failure; - - /** - * Internal list of test cases. - * Use ucx_test_register() to add tests to this list. - */ - UcxTestList *tests; -}; - -/** - * Creates a new test suite. - * @return a new test suite - */ -UcxTestSuite* ucx_test_suite_new(); - -/** - * Destroys a test suite. - * @param suite the test suite to destroy - */ -void ucx_test_suite_free(UcxTestSuite* suite); - -/** - * Registers a test function with the specified test suite. - * - * @param suite the suite, the test function shall be added to - * @param test the test function to register - * @return EXIT_SUCCESS on success or - * EXIT_FAILURE on failure - */ -int ucx_test_register(UcxTestSuite* suite, UcxTest test); - -/** - * Runs a test suite and writes the test log to the specified stream. - * @param suite the test suite to run - * @param outstream the stream the log shall be written to - */ -void ucx_test_run(UcxTestSuite* suite, FILE* outstream); - -/** - * Macro for a #UcxTest function header. - * - * Use this macro to declare and/or define a #UcxTest function. - * - * @param name the name of the test function - */ -#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_) - -/** - * Marks the begin of a test. - * Note: Any UCX_TEST_ASSERT() calls must be performed after - * #UCX_TEST_BEGIN. - * - * @see #UCX_TEST_END - */ -#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\ - fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ - fwrite("... ", 1, 4, _output_);\ - jmp_buf _env_; \ - if (!setjmp(_env_)) { - -/** - * Checks a test assertion. - * If the assertion is correct, the test carries on. If the assertion is not - * correct, the specified message (terminated by a dot and a line break) is - * written to the test suites output stream. - * @param condition the condition to check - * @param message the message that shall be printed out on failure - */ -#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \ - fwrite(message".\n", 1, 2+strlen(message), _output_); \ - _suite_->failure++; \ - longjmp(_env_, 1);\ - } - -/** - * Macro for a test subroutine function header. - * - * Use this to declare and/or define a subroutine that can be called by using - * UCX_TEST_CALL_SUBROUTINE(). - * - * @param name the name of the subroutine - * @param ... the parameter list - * - * @see UCX_TEST_CALL_SUBROUTINE() - */ -#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\ - FILE *_output_, jmp_buf _env_, __VA_ARGS__) - -/** - * Macro for calling a test subroutine. - * - * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this - * macro. - * - * Note: You may only call subroutines within a #UCX_TEST_BEGIN- - * #UCX_TEST_END-block. - * - * @param name the name of the subroutine - * @param ... the argument list - * - * @see UCX_TEST_SUBROUTINE() - */ -#define UCX_TEST_CALL_SUBROUTINE(name,...) \ - name(_suite_,_output_,_env_,__VA_ARGS__); - -/** - * Marks the end of a test. - * Note: Any UCX_TEST_ASSERT() calls must be performed before - * #UCX_TEST_END. - * - * @see #UCX_TEST_BEGIN - */ -#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;} - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_TEST_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx.c --- a/src/ucx/ucx.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/ucx.c Sat Sep 24 16:26:10 2022 +0200 @@ -2,14 +2,23 @@ * @mainpage UAP Common Extensions * Library with common and useful functions, macros and data structures. *

- * Latest available source:
+ * Latest available source:
+ * + * https://sourceforge.net/projects/ucx/files/ + *

+ * + *

+ * Repositories:
+ * + * https://sourceforge.net/p/ucx/code + * - or - * * https://develop.uap-core.de/hg/ucx *

* *

LICENCE

* - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,4 +43,20 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "ucx.h" +#include "ucx/ucx.h" + +int ucx_szmul_impl(size_t a, size_t b, size_t *result) { + if(a == 0 || b == 0) { + *result = 0; + return 0; + } + size_t r = a * b; + if(r / b == a) { + *result = r; + return 0; + } else { + *result = 0; + return 1; + } +} + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx.h --- a/src/ucx/ucx.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Main UCX Header providing most common definitions. - * - * @file ucx.h - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_H -#define UCX_H - -/** Major UCX version as integer constant. */ -#define UCX_VERSION_MAJOR 0 - -/** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 10 - -#include - -#ifdef _WIN32 -#if !(defined __ssize_t_defined || defined _SSIZE_T_) -#include -typedef SSIZE_T ssize_t; -#define __ssize_t_defined -#define _SSIZE_T_ -#endif /* __ssize_t_defined and _SSIZE_T */ -#else /* !_WIN32 */ -#include -#endif /* _WIN32 */ - -#ifdef __cplusplus -#ifndef _Bool -#define _Bool bool -#define restrict -#endif -/** Use C naming even when compiling with C++. */ -#define UCX_EXTERN extern "C" -extern "C" { -#else -/** Pointless in C. */ -#define UCX_EXTERN -#endif - - -/** - * A function pointer to a destructor function. - * @see ucx_mempool_setdestr() - * @see ucx_mempool_regdestr() - */ -typedef void(*ucx_destructor)(void*); - -/** - * Function pointer to a compare function. - * - * The compare function shall take three arguments: the two values that shall be - * compared and optional additional data. - * The function shall then return -1 if the first argument is less than the - * second argument, 1 if the first argument is greater than the second argument - * and 0 if both arguments are equal. If the third argument is - * NULL, it shall be ignored. - */ -typedef int(*cmp_func)(void*,void*,void*); - -/** - * Function pointer to a copy function. - * - * The copy function shall create a copy of the first argument and may use - * additional data provided by the second argument. If the second argument is - * NULL, it shall be ignored. - - * Attention: if pointers returned by functions of this type may be - * passed to free() depends on the implementation of the - * respective copy_func. - */ -typedef void*(*copy_func)(void*,void*); - -/** - * Function pointer to a write function. - * - * The signature of the write function shall be compatible to the signature - * of standard fwrite, though it may use arbitrary data types for - * source and destination. - * - * The arguments shall contain (in ascending order): a pointer to the source, - * the length of one element, the element count and a pointer to the - * destination. - */ -typedef size_t(*write_func)(const void*, size_t, size_t, void*); - -/** - * Function pointer to a read function. - * - * The signature of the read function shall be compatible to the signature - * of standard fread, though it may use arbitrary data types for - * source and destination. - * - * The arguments shall contain (in ascending order): a pointer to the - * destination, the length of one element, the element count and a pointer to - * the source. - */ -typedef size_t(*read_func)(void*, size_t, size_t, void*); - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_H */ - diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/allocator.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/allocator.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,206 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Allocator for custom memory management. + * + * A UCX allocator consists of a pointer to the memory area / pool and four + * function pointers to memory management functions operating on this memory + * area / pool. These functions shall behave equivalent to the standard libc + * functions malloc(), calloc(), realloc() and free(). + * + * The signature of the memory management functions is based on the signature + * of the respective libc function but each of them takes the pointer to the + * memory area / pool as first argument. + * + * As the pointer to the memory area / pool can be arbitrarily chosen, any data + * can be provided to the memory management functions. A UcxMempool is just + * one example. + * + * @see mempool.h + * @see UcxMap + * + * @file allocator.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_ALLOCATOR_H +#define UCX_ALLOCATOR_H + +#include "ucx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A function pointer to the allocators malloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_malloc)(void *pool, size_t n); + +/** + * A function pointer to the allocators calloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size); + +/** + * A function pointer to the allocators realloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n); + +/** + * A function pointer to the allocators free() function. + * @see UcxAllocator + */ +typedef void(*ucx_allocator_free)(void *pool, void *data); + +/** + * UCX allocator data structure containing memory management functions. + */ +typedef struct { + /** Pointer to an area of memory or a complex memory pool. + * This pointer will be passed to any memory management function as first + * argument. + */ + void *pool; + /** + * The malloc() function for this allocator. + */ + ucx_allocator_malloc malloc; + /** + * The calloc() function for this allocator. + */ + ucx_allocator_calloc calloc; + /** + * The realloc() function for this allocator. + */ + ucx_allocator_realloc realloc; + /** + * The free() function for this allocator. + */ + ucx_allocator_free free; +} UcxAllocator; + +/** + * Returns a pointer to the default allocator. + * + * The default allocator contains wrappers to the standard libc memory + * management functions. Use this function to get a pointer to a globally + * available allocator. You may also define an own UcxAllocator by assigning + * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable + * to any function that takes a UcxAllocator as argument. Note that using + * this function is the recommended way of passing a default allocator, thus + * it never runs out of scope. + * + * @return a pointer to the default allocator + * + * @see UCX_ALLOCATOR_DEFAULT + */ +UcxAllocator *ucx_default_allocator(); + +/** + * A wrapper for the standard libc malloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to malloc() + * @return return value of malloc() + */ +void *ucx_default_malloc(void *ignore, size_t n); +/** + * A wrapper for the standard libc calloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to calloc() + * @param size argument passed to calloc() + * @return return value of calloc() + */ +void *ucx_default_calloc(void *ignore, size_t n, size_t size); +/** + * A wrapper for the standard libc realloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argumend passed to realloc() + * @param n argument passed to realloc() + * @return return value of realloc() + */ +void *ucx_default_realloc(void *ignore, void *data, size_t n); +/** + * A wrapper for the standard libc free() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argument passed to free() + */ +void ucx_default_free(void *ignore, void *data); + +/** + * Shorthand for calling an allocators malloc function. + * @param allocator the allocator to use + * @param n size of space to allocate + * @return a pointer to the allocated memory area + */ +#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n)) + +/** + * Shorthand for calling an allocators calloc function. + * @param allocator the allocator to use + * @param n the count of elements the space should be allocated for + * @param size the size of each element + * @return a pointer to the allocated memory area + */ +#define alcalloc(allocator, n, size) \ + ((allocator)->calloc((allocator)->pool, n, size)) + +/** + * Shorthand for calling an allocators realloc function. + * @param allocator the allocator to use + * @param ptr the pointer to the memory area that shall be reallocated + * @param n the new size of the allocated memory area + * @return a pointer to the reallocated memory area + */ +#define alrealloc(allocator, ptr, n) \ + ((allocator)->realloc((allocator)->pool, ptr, n)) + +/** + * Shorthand for calling an allocators free function. + * @param allocator the allocator to use + * @param ptr the pointer to the memory area that shall be freed + */ +#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr)) + +/** + * Convenient macro for a default allocator struct definition. + */ +#define UCX_ALLOCATOR_DEFAULT {NULL, \ + ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \ + ucx_default_free } + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ALLOCATOR_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/array.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/array.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,460 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Dynamically allocated array implementation. + * + * @file array.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_ARRAY_H +#define UCX_ARRAY_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX array type. + */ +typedef struct { + /** + * The current capacity of the array. + */ + size_t capacity; + /** + * The actual number of elements in the array. + */ + size_t size; + /** + * The size of an individual element in bytes. + */ + size_t elemsize; + /** + * A pointer to the data. + */ + void* data; + /** + * The allocator used for the data. + */ + UcxAllocator* allocator; +} UcxArray; + +/** + * Sets an element in an arbitrary user defined array. + * The data is copied from the specified data location. + * + * If the capacity is insufficient, the array is automatically reallocated and + * the possibly new pointer is stored in the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param elmsize the size of each element + * @param idx the index of the element to set + * @param data a pointer to the element data + * @return zero on success or non-zero on error (errno will be set) + */ +#define ucx_array_util_set(array, capacity, elmsize, idx, data) \ + ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \ + elmsize, idx, data) + +/** + * Sets an element in an arbitrary user defined array. + * The data is copied from the specified data location. + * + * If the capacity is insufficient, the array is automatically reallocated + * using the specified allocator and the possibly new pointer is stored in + * the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param alloc the allocator that shall be used to reallocate the array + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param elmsize the size of each element + * @param idx the index of the element to set + * @param data a pointer to the element data + * @return zero on success or non-zero on error (errno will be set) + */ +int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t elmsize, size_t idx, void* data); + +/** + * Stores a pointer in an arbitrary user defined array. + * The element size of the array must be sizeof(void*). + * + * If the capacity is insufficient, the array is automatically reallocated and + * the possibly new pointer is stored in the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param idx the index of the element to set + * @param ptr the pointer to store + * @return zero on success or non-zero on error (errno will be set) + */ +#define ucx_array_util_setptr(array, capacity, idx, ptr) \ + ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \ + capacity, idx, ptr) + +/** + * Stores a pointer in an arbitrary user defined array. + * The element size of the array must be sizeof(void*). + * + * If the capacity is insufficient, the array is automatically reallocated + * using the specified allocator and the possibly new pointer is stored in + * the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param alloc the allocator that shall be used to reallocate the array + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param idx the index of the element to set + * @param ptr the pointer to store + * @return zero on success or non-zero on error (errno will be set) + */ +int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t idx, void* ptr); + + +/** + * Creates a new UCX array with the given capacity and element size. + * @param capacity the initial capacity + * @param elemsize the element size + * @return a pointer to a new UCX array structure + */ +UcxArray* ucx_array_new(size_t capacity, size_t elemsize); + +/** + * Creates a new UCX array using the specified allocator. + * + * @param capacity the initial capacity + * @param elemsize the element size + * @param allocator the allocator to use + * @return a pointer to new UCX array structure + */ +UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize, + UcxAllocator* allocator); + +/** + * Initializes a UCX array structure with the given capacity and element size. + * The structure must be uninitialized as the data pointer will be overwritten. + * + * @param array the structure to initialize + * @param capacity the initial capacity + * @param elemsize the element size + */ +void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize); + +/** + * Initializes a UCX array structure using the specified allocator. + * The structure must be uninitialized as the data pointer will be overwritten. + * + * @param array the structure to initialize + * @param capacity the initial capacity + * @param elemsize the element size + * @param allocator the allocator to use + */ +void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize, + UcxAllocator* allocator); + +/** + * Creates an shallow copy of an array. + * + * This function clones the specified array by using memcpy(). + * If the destination capacity is insufficient, an automatic reallocation is + * attempted. + * + * Note: if the destination array is uninitialized, the behavior is undefined. + * + * @param dest the array to copy to + * @param src the array to copy from + * @return zero on success, non-zero on reallocation failure. + */ +int ucx_array_clone(UcxArray* dest, UcxArray const* src); + + +/** + * Compares two UCX arrays element-wise by using a compare function. + * + * Elements of the two specified arrays are compared by using the specified + * compare function and the additional data. The type and content of this + * additional data depends on the cmp_func() used. + * + * This function always returns zero, if the element sizes of the arrays do + * not match and performs no comparisons in this case. + * + * @param array1 the first array + * @param array2 the second array + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the two arrays equal element-wise, 0 otherwise + */ +int ucx_array_equals(UcxArray const *array1, UcxArray const *array2, + cmp_func cmpfnc, void* data); + +/** + * Destroys the array. + * + * The data is freed and both capacity and count are reset to zero. + * If the array structure itself has been dynamically allocated, it has to be + * freed separately. + * + * @param array the array to destroy + */ +void ucx_array_destroy(UcxArray *array); + +/** + * Destroys and frees the array. + * + * @param array the array to free + */ +void ucx_array_free(UcxArray *array); + +/** + * Inserts elements at the end of the array. + * + * This is an O(1) operation. + * The array will automatically grow, if the capacity is exceeded. + * If a pointer to data is provided, the data is copied into the array with + * memcpy(). Otherwise the new elements are completely zeroed. + * + * @param array a pointer the array where to append the data + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, zeroed elements are appended) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_set_from() + * @see ucx_array_append() + */ +int ucx_array_append_from(UcxArray *array, void *data, size_t count); + + +/** + * Inserts elements at the beginning of the array. + * + * This is an expensive operation, because the contents must be moved. + * If there is no particular reason to prepend data, you should use + * ucx_array_append_from() instead. + * + * @param array a pointer the array where to prepend the data + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, zeroed elements are inserted) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_append_from() + * @see ucx_array_set_from() + * @see ucx_array_prepend() + */ +int ucx_array_prepend_from(UcxArray *array, void *data, size_t count); + + +/** + * Sets elements starting at the specified index. + * + * If the any index is out of bounds, the array automatically grows. + * The pointer to the data may be NULL, in which case the elements are zeroed. + * + * @param array a pointer the array where to set the data + * @param index the index of the element to set + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, the memory in the array is zeroed) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_append_from() + * @see ucx_array_set() + */ +int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count); + +/** + * Concatenates two arrays. + * + * The contents of the second array are appended to the first array in one + * single operation. The second array is otherwise left untouched. + * + * The first array may grow automatically. If this fails, both arrays remain + * unmodified. + * + * @param array1 first array + * @param array2 second array + * @return zero on success, non-zero if reallocation was necessary but failed + * or the element size does not match + */ +int ucx_array_concat(UcxArray *array1, const UcxArray *array2); + +/** + * Returns a pointer to the array element at the specified index. + * + * @param array the array to retrieve the element from + * @param index index of the element to return + * @return a pointer to the element at the specified index or NULL, + * if the index is greater than the array size + */ +void *ucx_array_at(UcxArray const* array, size_t index); + +/** + * Returns the index of an element containing the specified data. + * + * This function uses a cmp_func() to compare the data of each list element + * with the specified data. If no cmp_func is provided, memcmp() is used. + * + * If the array contains the data more than once, the index of the first + * occurrence is returned. + * If the array does not contain the data, the size of array is returned. + * + * @param array the array where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return the index of the element containing the specified data or the size of + * the array, if the data is not found in this array + */ +size_t ucx_array_find(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Checks, if an array contains a specific element. + * + * An element is found, if ucx_array_find() returns a value less than the size. + * + * @param array the array where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the array contains the specified element data + * @see ucx_array_find() + */ +int ucx_array_contains(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Sorts a UcxArray with the best available sort algorithm. + * + * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS). + * The order of arguments is automatically adjusted for the FreeBSD and MacOS + * version of qsort_r(). + * + * If qsort_r() is not available, a merge sort algorithm is used, which is + * guaranteed to use no more additional memory than for exactly one element. + * + * @param array the array to sort + * @param cmpfnc the function that shall be used to compare the element data + * @param data additional data for the cmp_func() or NULL + */ +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data); + +/** + * Removes an element from the array. + * + * This is in general an expensive operation, because several elements may + * be moved. If the order of the elements is not relevant, use + * ucx_array_remove_fast() instead. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove(UcxArray *array, size_t index); + +/** + * Removes an element from the array. + * + * This is an O(1) operation, but does not maintain the order of the elements. + * The last element in the array is moved to the location of the removed + * element. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove_fast(UcxArray *array, size_t index); + +/** + * Shrinks the memory to exactly fit the contents. + * + * After this operation, the capacity equals the size. + * + * @param array a pointer to the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_shrink(UcxArray* array); + +/** + * Sets the capacity of the array. + * + * If the new capacity is smaller than the size of the array, the elements + * are removed and the size is adjusted accordingly. + * + * @param array a pointer to the array + * @param capacity the new capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_resize(UcxArray* array, size_t capacity); + +/** + * Resizes the array only, if the capacity is insufficient. + * + * If the requested capacity is smaller than the current capacity, this + * function does nothing. + * + * @param array a pointer to the array + * @param capacity the guaranteed capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_reserve(UcxArray* array, size_t capacity); + +/** + * Resizes the capacity, if the specified number of elements would not fit. + * + * A call to ucx_array_grow(array, count) is effectively the same as + * ucx_array_reserve(array, array->size+count). + * + * @param array a pointer to the array + * @param count the number of elements that should additionally fit + * into the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_grow(UcxArray* array, size_t count); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ARRAY_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/avl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/avl.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,353 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @file avl.h + * + * AVL tree implementation. + * + * This binary search tree implementation allows average O(1) insertion and + * removal of elements (excluding binary search time). + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_AVL_H +#define UCX_AVL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX AVL Node type. + * + * @see UcxAVLNode + */ +typedef struct UcxAVLNode UcxAVLNode; + +/** + * UCX AVL Node. + */ +struct UcxAVLNode { + /** + * The key for this node. + */ + intptr_t key; + /** + * Data contained by this node. + */ + void *value; + /** + * The height of this (sub)-tree. + */ + size_t height; + /** + * Parent node. + */ + UcxAVLNode *parent; + /** + * Root node of left subtree. + */ + UcxAVLNode *left; + /** + * Root node of right subtree. + */ + UcxAVLNode *right; +}; + +/** + * UCX AVL Tree. + */ +typedef struct { + /** + * The UcxAllocator that shall be used to manage the memory for node data. + */ + UcxAllocator *allocator; + /** + * Root node of the tree. + */ + UcxAVLNode *root; + /** + * Compare function that shall be used to compare the UcxAVLNode keys. + * @see UcxAVLNode.key + */ + cmp_func cmpfunc; + /** + * Custom user data. + * This data will also be provided to the cmpfunc. + */ + void *userdata; +} UcxAVLTree; + +/** + * Initializes a new UcxAVLTree with a default allocator. + * + * @param cmpfunc the compare function that shall be used + * @return a new UcxAVLTree object + * @see ucx_avl_new_a() + */ +UcxAVLTree *ucx_avl_new(cmp_func cmpfunc); + +/** + * Initializes a new UcxAVLTree with the specified allocator. + * + * The cmpfunc should be capable of comparing two keys within this AVL tree. + * So if you want to use null terminated strings as keys, you could use the + * ucx_cmp_str() function here. + * + * @param cmpfunc the compare function that shall be used + * @param allocator the UcxAllocator that shall be used + * @return a new UcxAVLTree object + */ +UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator); + +/** + * Destroys a UcxAVLTree. + * + * Note, that the contents are not automatically freed. + * Use may use #ucx_avl_free_content() before calling this function. + * + * @param tree the tree to destroy + * @see ucx_avl_free_content() + */ +void ucx_avl_free(UcxAVLTree *tree); + +/** + * Frees the contents of a UcxAVLTree. + * + * This is a convenience function that iterates over the tree and passes all + * values to the specified destructor function. + * + * If no destructor is specified (NULL), the free() function of + * the tree's own allocator is used. + * + * You must ensure, that it is valid to pass each value in the map to the same + * destructor function. + * + * You should free the entire tree afterwards, as the contents will be invalid. + * + * @param tree for which the contents shall be freed + * @param destr optional pointer to a destructor function + * @see ucx_avl_free() + */ +void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr); + +/** + * Macro for initializing a new UcxAVLTree with the default allocator and a + * ucx_cmp_ptr() compare function. + * + * @return a new default UcxAVLTree object + */ +#define ucx_avl_default_new() \ + ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator()) + +/** + * Gets the node from the tree, that is associated with the specified key. + * @param tree the UcxAVLTree + * @param key the key + * @return the node (or NULL, if the key is not present) + */ +UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key); + +/** + * Gets the value from the tree, that is associated with the specified key. + * @param tree the UcxAVLTree + * @param key the key + * @return the value (or NULL, if the key is not present) + */ +void *ucx_avl_get(UcxAVLTree *tree, intptr_t key); + +/** + * A mode for #ucx_avl_find_node() with the same behavior as + * #ucx_avl_get_node(). + */ +#define UCX_AVL_FIND_EXACT 0 +/** + * A mode for #ucx_avl_find_node() finding the node whose key is at least + * as large as the specified key. + */ +#define UCX_AVL_FIND_LOWER_BOUNDED 1 +/** + * A mode for #ucx_avl_find_node() finding the node whose key is at most + * as large as the specified key. + */ +#define UCX_AVL_FIND_UPPER_BOUNDED 2 +/** + * A mode for #ucx_avl_find_node() finding the node with a key that is as close + * to the specified key as possible. If the key is present, the behavior is + * like #ucx_avl_get_node(). This mode only returns NULL on + * empty trees. + */ +#define UCX_AVL_FIND_CLOSEST 3 + +/** + * Finds a node within the tree. The following modes are supported: + *
    + *
  • #UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()
  • + *
  • #UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least + * as large as the specified key
  • + *
  • #UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most + * as large as the specified key
  • + *
  • #UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to + * the specified key as possible. If the key is present, the behavior is + * like #ucx_avl_get_node(). This mode only returns NULL on + * empty trees.
  • + *
+ * + * The distance function provided MUST agree with the compare function of + * the AVL tree. + * + * @param tree the UcxAVLTree + * @param key the key + * @param dfnc the distance function + * @param mode the find mode + * @return the node (or NULL, if no node can be found) + */ +UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode); + +/** + * Finds a value within the tree. + * See #ucx_avl_find_node() for details. + * + * @param tree the UcxAVLTree + * @param key the key + * @param dfnc the distance function + * @param mode the find mode + * @return the value (or NULL, if no value can be found) + */ +void *ucx_avl_find(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode); + +/** + * Puts a key/value pair into the tree. + * + * Attention: use this function only, if a possible old value does not need + * to be preserved. + * + * @param tree the UcxAVLTree + * @param key the key + * @param value the new value + * @return zero, if and only if the operation succeeded + */ +int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value); + +/** + * Puts a key/value pair into the tree. + * + * This is a secure function which saves the old value to the variable pointed + * at by oldvalue. + * + * @param tree the UcxAVLTree + * @param key the key + * @param value the new value + * @param oldvalue optional: a pointer to the location where a possible old + * value shall be stored + * @return zero, if and only if the operation succeeded + */ +int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue); + +/** + * Removes a node from the AVL tree. + * + * Note: the specified node is logically removed. The tree implementation + * decides which memory area is freed. In most cases the here provided node + * is freed, so its further use is generally undefined. + * + * @param tree the UcxAVLTree + * @param node the node to remove + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node); + +/** + * Removes an element from the AVL tree. + * + * @param tree the UcxAVLTree + * @param key the key + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove(UcxAVLTree *tree, intptr_t key); + +/** + * Removes an element from the AVL tree. + * + * This is a secure function which saves the old key and value data from node + * to the variables at the location of oldkey and oldvalue (if specified), so + * they can be freed afterwards (if necessary). + * + * Note: the returned key in oldkey is possibly not the same as the provided + * key for the lookup (in terms of memory location). + * + * @param tree the UcxAVLTree + * @param key the key of the element to remove + * @param oldkey optional: a pointer to the location where the old key shall be + * stored + * @param oldvalue optional: a pointer to the location where the old value + * shall be stored + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key, + intptr_t *oldkey, void **oldvalue); + +/** + * Counts the nodes in the specified UcxAVLTree. + * @param tree the AVL tree + * @return the node count + */ +size_t ucx_avl_count(UcxAVLTree *tree); + +/** + * Finds the in-order predecessor of the given node. + * @param node an AVL node + * @return the in-order predecessor of the given node, or NULL if + * the given node is the in-order minimum + */ +UcxAVLNode* ucx_avl_pred(UcxAVLNode* node); + +/** + * Finds the in-order successor of the given node. + * @param node an AVL node + * @return the in-order successor of the given node, or NULL if + * the given node is the in-order maximum + */ +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_AVL_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/buffer.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,339 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file buffer.h + * + * Advanced buffer implementation. + * + * Instances of UcxBuffer can be used to read from or to write to like one + * would do with a stream. This allows the use of ucx_stream_copy() to copy + * contents from one buffer to another. + * + * Some features for convenient use of the buffer + * can be enabled. See the documentation of the macro constants for more + * information. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_BUFFER_H +#define UCX_BUFFER_H + +#include "ucx.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * No buffer features enabled (all flags cleared). + */ +#define UCX_BUFFER_DEFAULT 0x00 + +/** + * If this flag is enabled, the buffer will automatically free its contents. + */ +#define UCX_BUFFER_AUTOFREE 0x01 + +/** + * If this flag is enabled, the buffer will automatically extends its capacity. + */ +#define UCX_BUFFER_AUTOEXTEND 0x02 + +/** UCX Buffer. */ +typedef struct { + /** A pointer to the buffer contents. */ + char *space; + /** Current position of the buffer. */ + size_t pos; + /** Current capacity (i.e. maximum size) of the buffer. */ + size_t capacity; + /** Current size of the buffer content. */ + size_t size; + /** + * Flag register for buffer features. + * @see #UCX_BUFFER_DEFAULT + * @see #UCX_BUFFER_AUTOFREE + * @see #UCX_BUFFER_AUTOEXTEND + */ + int flags; +} UcxBuffer; + +/** + * Creates a new buffer. + * + * Note: you may provide NULL as argument for + * space. Then this function will allocate the space and enforce + * the #UCX_BUFFER_AUTOFREE flag. + * + * @param space pointer to the memory area, or NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param flags buffer features (see UcxBuffer.flags) + * @return the new buffer + */ +UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags); + +/** + * Destroys a buffer. + * + * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer + * are also freed. + * + * @param buffer the buffer to destroy + */ +void ucx_buffer_free(UcxBuffer* buffer); + +/** + * Creates a new buffer and fills it with extracted content from another buffer. + * + * Note: the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer. + * + * @param src the source buffer + * @param start the start position of extraction + * @param length the count of bytes to extract (must not be zero) + * @param flags feature mask for the new buffer + * @return a new buffer containing the extraction + */ +UcxBuffer* ucx_buffer_extract(UcxBuffer *src, + size_t start, size_t length, int flags); + +/** + * A shorthand macro for the full extraction of the buffer. + * + * @param src the source buffer + * @param flags feature mask for the new buffer + * @return a new buffer with the extracted content + */ +#define ucx_buffer_clone(src,flags) \ + ucx_buffer_extract(src, 0, (src)->capacity, flags) + + +/** + * Shifts the contents of the buffer by the given offset. + * + * If the offset is positive, the contents are shifted to the right. + * If auto extension is enabled, the buffer grows, if necessary. + * In case the auto extension fails, this function returns a non-zero value and + * no contents are changed. + * If auto extension is disabled, the contents that do not fit into the buffer + * are discarded. + * + * If the offset is negative, the contents are shifted to the left where the + * first shift bytes are discarded. + * The new size of the buffer is the old size minus + * the absolute shift value. + * If this value is larger than the buffer size, the buffer is emptied (but + * not cleared, see the security note below). + * + * The buffer position gets shifted alongside with the content but is kept + * within the boundaries of the buffer. + * + * Security note: the shifting operation does not erase the + * previously occupied memory cells. You can easily do that manually, e.g. by + * calling memset(buffer->space, 0, shift) for a right shift or + * memset(buffer->size, 0, buffer->capacity-buffer->size) + * for a left shift. + * + * @param buffer the buffer + * @param shift the shift offset (negative means left shift) + * @return 0 on success, non-zero if a required auto-extension fails + */ +int ucx_buffer_shift(UcxBuffer* buffer, off_t shift); + +/** + * Shifts the buffer to the right. + * See ucx_buffer_shift() for details. + * + * @param buffer the buffer + * @param shift the shift offset + * @return 0 on success, non-zero if a required auto-extension fails + * @see ucx_buffer_shift() + */ +int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift); + +/** + * Shifts the buffer to the left. + * + * See ucx_buffer_shift() for details. Note, however, that this method expects + * a positive shift offset. + * + * Since a left shift cannot fail due to memory allocation problems, this + * function always returns zero. + * + * @param buffer the buffer + * @param shift the shift offset + * @return always zero + * @see ucx_buffer_shift() + */ +int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift); + + +/** + * Moves the position of the buffer. + * + * The new position is relative to the whence argument. + * + * SEEK_SET marks the start of the buffer. + * SEEK_CUR marks the current position. + * SEEK_END marks the end of the buffer. + * + * With an offset of zero, this function sets the buffer position to zero + * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position + * unchanged (SEEK_CUR). + * + * @param buffer + * @param offset position offset relative to whence + * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END + * @return 0 on success, non-zero if the position is invalid + * + */ +int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence); + +/** + * Clears the buffer by resetting the position and deleting the data. + * + * The data is deleted by a zeroing it with call to memset(). + * + * @param buffer the buffer to be cleared + */ +#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \ + (buffer)->size = 0; (buffer)->pos = 0; + +/** + * Tests, if the buffer position has exceeded the buffer capacity. + * + * @param buffer the buffer to test + * @return non-zero, if the current buffer position has exceeded the last + * available byte of the buffer. + */ +int ucx_buffer_eof(UcxBuffer *buffer); + + +/** + * Extends the capacity of the buffer. + * + * Note: The buffer capacity increased by a power of two. I.e. + * the buffer capacity is doubled, as long as it would not hold the current + * content plus the additional required bytes. + * + * Attention: the argument provided is the number of additional + * bytes the buffer shall hold. It is NOT the total number of bytes the + * buffer shall hold. + * + * @param buffer the buffer to extend + * @param additional_bytes the number of additional bytes the buffer shall + * at least hold + * @return 0 on success or a non-zero value on failure + */ +int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes); + +/** + * Writes data to a UcxBuffer. + * + * The position of the buffer is increased by the number of bytes written. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the UcxBuffer to write to + * @return the total count of bytes written + */ +size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer); + +/** + * Reads data from a UcxBuffer. + * + * The position of the buffer is increased by the number of bytes read. + * + * @param ptr a pointer to the memory area where to store the read data + * @param size the length of one element + * @param nitems the element count + * @param buffer the UcxBuffer to read from + * @return the total number of elements read + */ +size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer); + +/** + * Writes a character to a buffer. + * + * The least significant byte of the argument is written to the buffer. If the + * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled, + * the buffer capacity is extended by ucx_buffer_extend(). If the feature is + * disabled or buffer extension fails, EOF is returned. + * + * On successful write the position of the buffer is increased. + * + * @param buffer the buffer to write to + * @param c the character to write as int value + * @return the byte that has bean written as int value or + * EOF when the end of the stream is reached and automatic + * extension is not enabled or not possible + */ +int ucx_buffer_putc(UcxBuffer *buffer, int c); + +/** + * Gets a character from a buffer. + * + * The current position of the buffer is increased after a successful read. + * + * @param buffer the buffer to read from + * @return the character as int value or EOF, if the + * end of the buffer is reached + */ +int ucx_buffer_getc(UcxBuffer *buffer); + +/** + * Writes a string to a buffer. + * + * @param buffer the buffer + * @param str the string + * @return the number of bytes written + */ +size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str); + +/** + * Returns the complete buffer content as sstr_t. + * @param buffer the buffer + * @return the result of sstrn() with the buffer space and size + * as arguments + */ +#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_BUFFER_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/list.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,512 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Doubly linked list implementation. + * + * @file list.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_LIST_H +#define UCX_LIST_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX lists. + * + * The first argument is the name of the iteration variable. The scope of + * this variable is limited to the UCX_FOREACH statement. + * + * The second argument is a pointer to the list. In most cases this will be the + * pointer to the first element of the list, but it may also be an arbitrary + * element of the list. The iteration will then start with that element. + * + * @param list The first element of the list + * @param elem The variable name of the element + */ +#define UCX_FOREACH(elem,list) \ + for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next) + +/** + * UCX list type. + * @see UcxList + */ +typedef struct UcxList UcxList; + +/** + * UCX list structure. + */ +struct UcxList { + /** + * List element payload. + */ + void *data; + /** + * Pointer to the next list element or NULL, if this is the + * last element. + */ + UcxList *next; + /** + * Pointer to the previous list element or NULL, if this is + * the first element. + */ + UcxList *prev; +}; + +/** + * Creates an element-wise copy of a list. + * + * This function clones the specified list by creating new list elements and + * copying the data with the specified copy_func(). If no copy_func() is + * specified, a shallow copy is created and the new list will reference the + * same data as the source list. + * + * @param list the list to copy + * @param cpyfnc a pointer to the function that shall copy an element (may be + * NULL) + * @param data additional data for the copy_func() + * @return a pointer to the copy + */ +UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data); + +/** + * Creates an element-wise copy of a list using a UcxAllocator. + * + * See ucx_list_clone() for details. + * + * You might want to pass the allocator via the data parameter, + * to access it within the copy function for making deep copies. + * + * @param allocator the allocator to use + * @param list the list to copy + * @param cpyfnc a pointer to the function that shall copy an element (may be + * NULL) + * @param data additional data for the copy_func() + * @return a pointer to the copy + * @see ucx_list_clone() + */ +UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list, + copy_func cpyfnc, void* data); + +/** + * Compares two UCX lists element-wise by using a compare function. + * + * Each element of the two specified lists are compared by using the specified + * compare function and the additional data. The type and content of this + * additional data depends on the cmp_func() used. + * + * If the list pointers denote elements within a list, the lists are compared + * starting with the denoted elements. Thus any previous elements are not taken + * into account. This might be useful to check, if certain list tails match + * each other. + * + * @param list1 the first list + * @param list2 the second list + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the two lists equal element-wise, 0 otherwise + */ +int ucx_list_equals(const UcxList *list1, const UcxList *list2, + cmp_func cmpfnc, void* data); + +/** + * Destroys the entire list. + * + * The members of the list are not automatically freed, so ensure they are + * otherwise referenced or destroyed by ucx_list_free_contents(). + * Otherwise, a memory leak is likely to occur. + * + * Caution: the argument MUST denote an entire list (i.e. a call + * to ucx_list_first() on the argument must return the argument itself) + * + * @param list the list to free + * @see ucx_list_free_contents() + */ +void ucx_list_free(UcxList *list); + +/** + * Destroys the entire list using a UcxAllocator. + * + * See ucx_list_free() for details. + * + * @param allocator the allocator to use + * @param list the list to free + * @see ucx_list_free() + */ +void ucx_list_free_a(UcxAllocator *allocator, UcxList *list); + +/** + * Destroys the contents of the specified list by calling the specified + * destructor on each of them. + * + * Note, that the contents are not usable afterwards and the list should be + * destroyed with ucx_list_free(). + * + * If no destructor is specified (NULL), stdlib's free() is used. + * + * @param list the list for which the contents shall be freed + * @param destr optional destructor function + * @see ucx_list_free() + */ +void ucx_list_free_content(UcxList* list, ucx_destructor destr); + + +/** + * Inserts an element at the end of the list. + * + * This is generally an O(n) operation, as the end of the list is retrieved with + * ucx_list_last(). + * + * @param list the list where to append the data, or NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL or a pointer to + * the newly created list otherwise + */ +UcxList *ucx_list_append(UcxList *list, void *data); + +/** + * Inserts an element at the end of the list using a UcxAllocator. + * + * See ucx_list_append() for details. + * + * @param allocator the allocator to use + * @param list the list where to append the data, or NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL or a pointer to + * the newly created list otherwise + * @see ucx_list_append() + */ +UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data); + + +/** + * Inserts an element at the beginning of the list. + * + * You should overwrite the old list pointer by calling + * mylist = ucx_list_prepend(mylist, mydata);. However, you may + * also perform successive calls of ucx_list_prepend() on the same list pointer, + * as this function always searchs for the head of the list with + * ucx_list_first(). + * + * @param list the list where to insert the data or NULL to create + * a new list + * @param data the data to insert + * @return a pointer to the new list head + */ +UcxList *ucx_list_prepend(UcxList *list, void *data); + +/** + * Inserts an element at the beginning of the list using a UcxAllocator. + * + * See ucx_list_prepend() for details. + * + * @param allocator the allocator to use + * @param list the list where to insert the data or NULL to create + * a new list + * @param data the data to insert + * @return a pointer to the new list head + * @see ucx_list_prepend() + */ +UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data); + +/** + * Concatenates two lists. + * + * Either of the two arguments may be NULL. + * + * This function modifies the references to the next/previous element of + * the last/first element of list1/ + * list2. + * + * @param list1 first list + * @param list2 second list + * @return if list1 is NULL, list2 is + * returned, otherwise list1 is returned + */ +UcxList *ucx_list_concat(UcxList *list1, UcxList *list2); + +/** + * Returns the first element of a list. + * + * If the argument is the list pointer, it is directly returned. Otherwise + * this function traverses to the first element of the list and returns the + * list pointer. + * + * @param elem one element of the list + * @return the first element of the list, the specified element is a member of + */ +UcxList *ucx_list_first(const UcxList *elem); + +/** + * Returns the last element of a list. + * + * If the argument has no successor, it is the last element and therefore + * directly returned. Otherwise this function traverses to the last element of + * the list and returns it. + * + * @param elem one element of the list + * @return the last element of the list, the specified element is a member of + */ +UcxList *ucx_list_last(const UcxList *elem); + +/** + * Returns the list element at the specified index. + * + * @param list the list to retrieve the element from + * @param index index of the element to return + * @return the element at the specified index or NULL, if the + * index is greater than the list size + */ +UcxList *ucx_list_get(const UcxList *list, size_t index); + +/** + * Returns the index of an element. + * + * @param list the list where to search for the element + * @param elem the element to find + * @return the index of the element or -1 if the list does not contain the + * element + */ +ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem); + +/** + * Returns the element count of the list. + * + * @param list the list whose elements are counted + * @return the element count + */ +size_t ucx_list_size(const UcxList *list); + +/** + * Returns the index of an element containing the specified data. + * + * This function uses a cmp_func() to compare the data of each list element + * with the specified data. If no cmp_func is provided, the pointers are + * compared. + * + * If the list contains the data more than once, the index of the first + * occurrence is returned. + * + * @param list the list where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return the index of the element containing the specified data or -1 if the + * data is not found in this list + */ +ssize_t ucx_list_find(const UcxList *list, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Checks, if a list contains a specific element. + * + * An element is found, if ucx_list_find() returns a value greater than -1. + * + * @param list the list where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the list contains the specified element data + * @see ucx_list_find() + */ +int ucx_list_contains(const UcxList *list, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Sorts a UcxList with natural merge sort. + * + * This function uses O(n) additional temporary memory for merge operations + * that is automatically freed after each merge. + * + * As the head of the list might change, you MUST call this function + * as follows: mylist = ucx_list_sort(mylist, mycmpfnc, mydata);. + * + * @param list the list to sort + * @param cmpfnc the function that shall be used to compare the element data + * @param data additional data for the cmp_func() + * @return the sorted list + */ +UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data); + +/** + * Removes an element from the list. + * + * If the first element is removed, the list pointer changes. So it is + * highly recommended to always update the pointer by calling + * mylist = ucx_list_remove(mylist, myelem);. + * + * @param list the list from which the element shall be removed + * @param element the element to remove + * @return returns the updated list pointer or NULL, if the list + * is now empty + */ +UcxList *ucx_list_remove(UcxList *list, UcxList *element); + +/** + * Removes an element from the list using a UcxAllocator. + * + * See ucx_list_remove() for details. + * + * @param allocator the allocator to use + * @param list the list from which the element shall be removed + * @param element the element to remove + * @return returns the updated list pointer or NULL, if the list + * @see ucx_list_remove() + */ +UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list, + UcxList *element); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LIST_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/logging.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/logging.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,253 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Logging API. + * + * @file logging.h + * @author Mike Becker, Olaf Wintermann + */ +#ifndef UCX_LOGGING_H +#define UCX_LOGGING_H + +#include "ucx.h" +#include "map.h" +#include "string.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* leave enough space for custom log levels */ + +/** Log level for error messages. */ +#define UCX_LOGGER_ERROR 0x00 + +/** Log level for warning messages. */ +#define UCX_LOGGER_WARN 0x10 + +/** Log level for information messages. */ +#define UCX_LOGGER_INFO 0x20 + +/** Log level for debug messages. */ +#define UCX_LOGGER_DEBUG 0x30 + +/** Log level for trace messages. */ +#define UCX_LOGGER_TRACE 0x40 + +/** + * Output flag for the log level. + * If this flag is set, the log message will contain the log level. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_LEVEL 0x01 + +/** + * Output flag for the timestmap. + * If this flag is set, the log message will contain the timestmap. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_TIMESTAMP 0x02 + +/** + * Output flag for the source. + * If this flag is set, the log message will contain the source file and line + * number. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_SOURCE 0x04 + +/** + * The UCX Logger object. + */ +typedef struct { + /** The stream this logger writes its messages to.*/ + void *stream; + + /** + * The write function that shall be used. + * For standard file or stdout loggers this might be standard fwrite + * (default). + */ + write_func writer; + + /** + * The date format for timestamp outputs including the delimiter + * (default: "%F %T %z "). + * @see UCX_LOGGER_TIMESTAMP + */ + char *dateformat; + + /** + * The level, this logger operates on. + * If a log command is issued, the message will only be logged, if the log + * level of the message is less or equal than the log level of the logger. + */ + unsigned int level; + + /** + * A configuration mask for automatic output. + * For each flag that is set, the logger automatically outputs some extra + * information like the timestamp or the source file and line number. + * See the documentation for the flags for details. + */ + unsigned int mask; + + /** + * A map of valid log levels for this logger. + * + * The keys represent all valid log levels and the values provide string + * representations, that are used, if the UCX_LOGGER_LEVEL flag is set. + * + * The exact data types are unsigned int for the key and + * const char* for the value. + * + * @see UCX_LOGGER_LEVEL + */ + UcxMap* levels; +} UcxLogger; + +/** + * Creates a new logger. + * @param stream the stream, which the logger shall write to + * @param level the level on which the logger shall operate + * @param mask configuration mask (cf. UcxLogger.mask) + * @return a new logger object + */ +UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask); + +/** + * Destroys the logger. + * + * The map containing the valid log levels is also automatically destroyed. + * + * @param logger the logger to destroy + */ +void ucx_logger_free(UcxLogger* logger); + +/** + * Internal log function - use macros instead. + * + * This function uses the format and variadic arguments for a + * printf()-style output of the log message. + * + * Dependent on the UcxLogger.mask some information is prepended. The complete + * format is: + * + * [LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message + * + * The source file name is reduced to the actual file name. This is necessary to + * get consistent behavior over different definitions of the __FILE__ macro. + * + * Attention: the message (including automatically generated information) + * is limited to 4096 characters. The level description is limited to + * 256 characters and the timestamp string is limited to 128 characters. + * + * @param logger the logger to use + * @param level the level to log on + * @param file information about the source file + * @param line information about the source line number + * @param format format string + * @param ... arguments + * @see ucx_logger_log() + */ +void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, + const unsigned int line, const char* format, ...); + +/** + * Registers a custom log level. + * @param logger the logger + * @param level the log level as unsigned integer + * @param name a string literal describing the level + */ +#define ucx_logger_register_level(logger, level, name) {\ + unsigned int l; \ + l = level; \ + ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \ + } while (0); + +/** + * Logs a message at the specified level. + * @param logger the logger to use + * @param level the level to log the message on + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_log(logger, level, ...) \ + ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__) + +/** + * Shortcut for logging an error message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_error(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__) + +/** + * Shortcut for logging an information message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_info(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__) + +/** + * Shortcut for logging a warning message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_warn(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__) + +/** + * Shortcut for logging a debug message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_debug(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__) + +/** + * Shortcut for logging a trace message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_trace(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LOGGING_H */ diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/map.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/map.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,549 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file map.h + * + * Hash map implementation. + * + * This implementation uses murmur hash 2 and separate chaining with linked + * lists. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MAP_H +#define UCX_MAP_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX maps. + * + * The key variable is implicitly defined, but the + * value variable must be already declared as type information + * cannot be inferred. + * + * @param key the variable name for the key + * @param value the variable name for the value + * @param iter a UcxMapIterator + * @see ucx_map_iterator() + */ +#define UCX_MAP_FOREACH(key,value,iter) \ + for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);) + +/** Type for the UCX map. @see UcxMap */ +typedef struct UcxMap UcxMap; + +/** Type for a key of a UcxMap. @see UcxKey */ +typedef struct UcxKey UcxKey; + +/** Type for an element of a UcxMap. @see UcxMapElement */ +typedef struct UcxMapElement UcxMapElement; + +/** Type for an iterator over a UcxMap. @see UcxMapIterator */ +typedef struct UcxMapIterator UcxMapIterator; + +/** Structure for the UCX map. */ +struct UcxMap { + /** An allocator that is used for the map elements. */ + UcxAllocator *allocator; + /** The array of map element lists. */ + UcxMapElement **map; + /** The size of the map is the length of the element list array. */ + size_t size; + /** The count of elements currently stored in this map. */ + size_t count; +}; + +/** Structure to publicly denote a key of a UcxMap. */ +struct UcxKey { + /** The key data. */ + const void *data; + /** The length of the key data. */ + size_t len; + /** A cache for the hash value of the key data. */ + int hash; +}; + +/** Internal structure for a key of a UcxMap. */ +struct UcxMapKey { + /** The key data. */ + void *data; + /** The length of the key data. */ + size_t len; + /** The hash value of the key data. */ + int hash; +}; + +/** Structure for an element of a UcxMap. */ +struct UcxMapElement { + /** The value data. */ + void *data; + + /** A pointer to the next element in the current list. */ + UcxMapElement *next; + + /** The corresponding key. */ + struct UcxMapKey key; +}; + +/** Structure for an iterator over a UcxMap. */ +struct UcxMapIterator { + /** The map to iterate over. */ + UcxMap const *map; + + /** The current map element. */ + UcxMapElement *cur; + + /** + * The current index of the element list array. + * Attention: this is NOT the element index! Do NOT + * manually iterate over the map by increasing this index. Use + * ucx_map_iter_next(). + * @see UcxMap.map*/ + size_t index; +}; + +/** + * Creates a new hash map with the specified size. + * @param size the size of the hash map + * @return a pointer to the new hash map + */ +UcxMap *ucx_map_new(size_t size); + +/** + * Creates a new hash map with the specified size using a UcxAllocator. + * @param allocator the allocator to use + * @param size the size of the hash map + * @return a pointer to the new hash map + */ +UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size); + +/** + * Frees a hash map. + * + * Note: the contents are not freed, use ucx_map_free_content() + * before calling this function to achieve that. + * + * @param map the map to be freed + * @see ucx_map_free_content() + */ +void ucx_map_free(UcxMap *map); + +/** + * Frees the contents of a hash map. + * + * This is a convenience function that iterates over the map and passes all + * values to the specified destructor function. + * + * If no destructor is specified (NULL), the free() function of + * the map's own allocator is used. + * + * You must ensure, that it is valid to pass each value in the map to the same + * destructor function. + * + * You should free or clear the map afterwards, as the contents will be invalid. + * + * @param map for which the contents shall be freed + * @param destr optional pointer to a destructor function + * @see ucx_map_free() + * @see ucx_map_clear() + */ +void ucx_map_free_content(UcxMap *map, ucx_destructor destr); + +/** + * Clears a hash map. + * + * Note: the contents are not freed, use ucx_map_free_content() + * before calling this function to achieve that. + * + * @param map the map to be cleared + * @see ucx_map_free_content() + */ +void ucx_map_clear(UcxMap *map); + + +/** + * Copies contents from a map to another map using a copy function. + * + * Note: The destination map does not need to be empty. However, if it + * contains data with keys that are also present in the source map, the contents + * are overwritten. + * + * @param from the source map + * @param to the destination map + * @param fnc the copy function or NULL if the pointer address + * shall be copied + * @param data additional data for the copy function + * @return 0 on success or a non-zero value on memory allocation errors + */ +int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data); + +/** + * Clones the map and rehashes if necessary. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always ensures a new UcxMap.size of at least + * 2.5*UcxMap.count. + * + * @param map the map to clone + * @param fnc the copy function to use or NULL if the new and + * the old map shall share the data pointers + * @param data additional data for the copy function + * @return the cloned map + * @see ucx_map_copy() + */ +UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data); + +/** + * Clones the map and rehashes if necessary. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always ensures a new UcxMap.size of at least + * 2.5*UcxMap.count. + * + * @param allocator the allocator to use for the cloned map + * @param map the map to clone + * @param fnc the copy function to use or NULL if the new and + * the old map shall share the data pointers + * @param data additional data for the copy function + * @return the cloned map + * @see ucx_map_copy() + */ +UcxMap *ucx_map_clone_a(UcxAllocator *allocator, + UcxMap const *map, copy_func fnc, void *data); + +/** + * Increases size of the hash map, if necessary. + * + * The load value is 0.75*UcxMap.size. If the element count exceeds the load + * value, the map needs to be rehashed. Otherwise no action is performed and + * this function simply returns 0. + * + * The rehashing process ensures, that the UcxMap.size is at least + * 2.5*UcxMap.count. So there is enough room for additional elements without + * the need of another soon rehashing. + * + * You can use this function to dramatically increase access performance. + * + * @param map the map to rehash + * @return 1, if a memory allocation error occurred, 0 otherwise + */ +int ucx_map_rehash(UcxMap *map); + +/** + * Puts a key/value-pair into the map. + * + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + */ +int ucx_map_put(UcxMap *map, UcxKey key, void *value); + +/** + * Retrieves a value by using a key. + * + * @param map the map + * @param key the key + * @return the value + */ +void* ucx_map_get(UcxMap const *map, UcxKey key); + +/** + * Removes a key/value-pair from the map by using the key. + * + * @param map the map + * @param key the key + * @return the removed value + */ +void* ucx_map_remove(UcxMap *map, UcxKey key); + +/** + * Shorthand for putting data with a sstr_t key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_sstr_put(map, key, value) \ + ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value) + +/** + * Shorthand for putting data with a C string key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_cstr_put(map, key, value) \ + ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value) + +/** + * Shorthand for putting data with an integer key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_int_put(map, key, value) \ + ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value) + +/** + * Shorthand for getting data from the map with a sstr_t key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_sstr_get(map, key) \ + ucx_map_get(map, ucx_key(key.ptr, key.length)) + +/** + * Shorthand for getting data from the map with a C string key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_cstr_get(map, key) \ + ucx_map_get(map, ucx_key(key, strlen(key))) + +/** + * Shorthand for getting data from the map with an integer key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_int_get(map, key) \ + ucx_map_get(map, ucx_key(&key, sizeof(int))) + +/** + * Shorthand for removing data from the map with a sstr_t key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_sstr_remove(map, key) \ + ucx_map_remove(map, ucx_key(key.ptr, key.length)) + +/** + * Shorthand for removing data from the map with a C string key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_cstr_remove(map, key) \ + ucx_map_remove(map, ucx_key(key, strlen(key))) + +/** + * Shorthand for removing data from the map with an integer key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_int_remove(map, key) \ + ucx_map_remove(map, ucx_key(&key, sizeof(key))) + +/** + * Creates a UcxKey based on the given data. + * + * This function implicitly computes the hash. + * + * @param data the data for the key + * @param len the length of the data + * @return a UcxKey with implicitly computed hash + * @see ucx_hash() + */ +UcxKey ucx_key(const void *data, size_t len); + +/** + * Computes a murmur hash-2. + * + * @param data the data to hash + * @param len the length of the data + * @return the murmur hash-2 of the data + */ +int ucx_hash(const char *data, size_t len); + +/** + * Creates an iterator for a map. + * + * Note: A UcxMapIterator iterates over all elements in all element + * lists successively. Therefore the order highly depends on the key hashes and + * may vary under different map sizes. So generally you may NOT rely on + * the iteration order. + * + * Note: The iterator is NOT initialized. You need to call + * ucx_map_iter_next() at least once before accessing any information. However, + * it is not recommended to access the fields of a UcxMapIterator directly. + * + * @param map the map to create the iterator for + * @return an iterator initialized on the first element of the + * first element list + * @see ucx_map_iter_next() + */ +UcxMapIterator ucx_map_iterator(UcxMap const *map); + +/** + * Proceeds to the next element of the map (if any). + * + * Subsequent calls on the same iterator proceed to the next element and + * store the key/value-pair into the memory specified as arguments of this + * function. + * + * If no further elements are found, this function returns zero and leaves the + * last found key/value-pair in memory. + * + * @param iterator the iterator to use + * @param key a pointer to the memory where to store the key + * @param value a pointer to the memory where to store the value + * @return 1, if another element was found, 0 if all elements has been processed + * @see ucx_map_iterator() + */ +int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MAP_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/mempool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/mempool.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,209 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mempool.h + * + * Memory pool implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MEMPOOL_H +#define UCX_MEMPOOL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX mempool structure. + */ +typedef struct { + /** UcxAllocator based on this pool */ + UcxAllocator *allocator; + + /** List of pointers to pooled memory. */ + void **data; + + /** Count of pooled memory items. */ + size_t ndata; + + /** Memory pool size. */ + size_t size; +} UcxMempool; + +/** Shorthand for a new default memory pool with a capacity of 16 elements. */ +#define ucx_mempool_new_default() ucx_mempool_new(16) + + +/** + * Creates a memory pool with the specified initial size. + * + * As the created memory pool automatically grows in size by factor two when + * trying to allocate memory on a full pool, it is recommended that you use + * a power of two for the initial size. + * + * @param n initial pool size (should be a power of two, e.g. 16) + * @return a pointer to the new memory pool + * @see ucx_mempool_new_default() + */ +UcxMempool *ucx_mempool_new(size_t n); + +/** + * Resizes a memory pool. + * + * This function will fail if the new capacity is not sufficient for the + * present data. + * + * @param pool the pool to resize + * @param newcap the new capacity + * @return zero on success or non-zero on failure + */ +int ucx_mempool_chcap(UcxMempool *pool, size_t newcap); + +/** + * Allocates pooled memory. + * + * @param pool the memory pool + * @param n amount of memory to allocate + * @return a pointer to the allocated memory + * @see ucx_allocator_malloc() + */ +void *ucx_mempool_malloc(UcxMempool *pool, size_t n); +/** + * Allocates a pooled memory array. + * + * The content of the allocated memory is set to zero. + * + * @param pool the memory pool + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @return a pointer to the allocated memory + * @see ucx_allocator_calloc() + */ +void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize); + +/** + * Reallocates pooled memory. + * + * If the memory to be reallocated is not contained by the specified pool, the + * behavior is undefined. + * + * @param pool the memory pool + * @param ptr a pointer to the memory that shall be reallocated + * @param n the new size of the memory + * @return a pointer to the new location of the memory + * @see ucx_allocator_realloc() + */ +void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n); + +/** + * Frees pooled memory. + * + * Before freeing the memory, the specified destructor function (if any) + * is called. + * + * If you specify memory, that is not pooled by the specified memory pool, the + * program will terminate with a call to abort(). + * + * @param pool the memory pool + * @param ptr a pointer to the memory that shall be freed + * @see ucx_mempool_set_destr() + */ +void ucx_mempool_free(UcxMempool *pool, void *ptr); + +/** + * Destroys a memory pool. + * + * For each element the destructor function (if any) is called and the element + * is freed. + * + * Each of the registered destructor function that has no corresponding element + * within the pool (namely those registered by ucx_mempool_reg_destr) is + * called interleaving with the element destruction, but with guarantee to the + * order in which they were registered (FIFO order). + * + * + * @param pool the mempool to destroy + */ +void ucx_mempool_destroy(UcxMempool *pool); + +/** + * Sets a destructor function for the specified memory. + * + * The destructor is automatically called when the memory is freed or the + * pool is destroyed. + * A destructor for pooled memory MUST NOT free the memory itself, + * as this is done by the pool. Use a destructor to free any resources + * managed by the pooled object. + * + * The only requirement for the specified memory is, that it MUST be + * pooled memory by a UcxMempool or an element-compatible mempool. The pointer + * to the destructor function is saved in a reserved area before the actual + * memory. + * + * @param ptr pooled memory + * @param func a pointer to the destructor function + * @see ucx_mempool_free() + * @see ucx_mempool_destroy() + */ +void ucx_mempool_set_destr(void *ptr, ucx_destructor func); + +/** + * Registers a destructor function for the specified (non-pooled) memory. + * + * This is useful, if you have memory that has not been allocated by a mempool, + * but shall be managed by a mempool. + * + * This function creates an entry in the specified mempool and the memory will + * therefore (logically) convert to pooled memory. + * However, this does not cause the memory to be freed automatically!. + * If you want to use this function, make the memory pool free non-pooled + * memory, the specified destructor function must call free() + * by itself. But keep in mind, that you then MUST NOT use this destructor + * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it + * would cause a double-free. + * + * @param pool the memory pool + * @param ptr data the destructor is registered for + * @param destr a pointer to the destructor function + */ +void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MEMPOOL_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/properties.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/properties.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,221 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * + * Load / store utilities for properties files. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "ucx.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UcxProperties object for parsing properties data. + * Most of the fields are for internal use only. You may configure the + * properties parser, e.g. by changing the used delimiter or specifying + * up to three different characters that shall introduce comments. + */ +typedef struct { + /** + * Input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + char *buffer; + + /** + * Length of the input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + size_t buflen; + + /** + * Current buffer position (don't set manually). + * Used by ucx_properties_next(). + */ + size_t pos; + + /** + * Internal temporary buffer (don't set manually). + * Used by ucx_properties_next(). + */ + char *tmp; + + /** + * Internal temporary buffer length (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmplen; + + /** + * Internal temporary buffer capacity (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmpcap; + + /** + * Parser error code. + * This is always 0 on success and a nonzero value on syntax errors. + * The value is set by ucx_properties_next(). + */ + int error; + + /** + * The delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; +} UcxProperties; + + +/** + * Constructs a new UcxProperties object. + * @return a pointer to the new UcxProperties object + */ +UcxProperties *ucx_properties_new(); + +/** + * Destroys a UcxProperties object. + * @param prop the UcxProperties object to destroy + */ +void ucx_properties_free(UcxProperties *prop); + +/** + * Sets the input buffer for the properties parser. + * + * After calling this function, you may parse the data by calling + * ucx_properties_next() until it returns 0. The function ucx_properties2map() + * is a convenience function that reads as much data as possible by using this + * function. + * + * + * @param prop the UcxProperties object + * @param buf a pointer to the new buffer + * @param len the payload length of the buffer + * @see ucx_properties_next() + * @see ucx_properties2map() + */ +void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); + +/** + * Retrieves the next key/value-pair. + * + * This function returns a nonzero value as long as there are key/value-pairs + * found. If no more key/value-pairs are found, you may refill the input buffer + * with ucx_properties_fill(). + * + * Attention: the sstr_t.ptr pointers of the output parameters point to + * memory within the input buffer of the parser and will get invalid some time. + * If you want long term copies of the key/value-pairs, use sstrdup() after + * calling this function. + * + * @param prop the UcxProperties object + * @param name a pointer to the sstr_t that shall contain the property name + * @param value a pointer to the sstr_t that shall contain the property value + * @return Nonzero, if a key/value-pair was successfully retrieved + * @see ucx_properties_fill() + */ +int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value); + +/** + * Retrieves all available key/value-pairs and puts them into a UcxMap. + * + * This is done by successive calls to ucx_properties_next() until no more + * key/value-pairs can be retrieved. + * + * The memory for the map values is allocated by the map's own allocator. + * + * @param prop the UcxProperties object + * @param map the target map + * @return The UcxProperties.error code (i.e. 0 on success). + * @see ucx_properties_fill() + * @see UcxMap.allocator + */ +int ucx_properties2map(UcxProperties *prop, UcxMap *map); + +/** + * Loads a properties file to a UcxMap. + * + * This is a convenience function that reads data from an input + * stream until the end of the stream is reached. + * + * @param map the map object to write the key/value-pairs to + * @param file the FILE* stream to read from + * @return 0 on success, or a non-zero value on error + * + * @see ucx_properties_fill() + * @see ucx_properties2map() + */ +int ucx_properties_load(UcxMap *map, FILE *file); + +/** + * Stores a UcxMap to a file. + * + * The key/value-pairs are written by using the following format: + * + * [key] = [value]\\n + * + * @param map the map to store + * @param file the FILE* stream to write to + * @return 0 on success, or a non-zero value on error + */ +int ucx_properties_store(UcxMap *map, FILE *file); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_PROPERTIES_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/stack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/stack.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,240 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file stack.h + * + * Default stack memory allocation implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STACK_H +#define UCX_STACK_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * UCX stack structure. + */ +typedef struct { + /** UcxAllocator based on this stack */ + UcxAllocator allocator; + + /** Stack size. */ + size_t size; + + /** Pointer to the bottom of the stack */ + char *space; + + /** Pointer to the top of the stack */ + char *top; +} UcxStack; + +/** + * Metadata for each UCX stack element. + */ +struct ucx_stack_metadata { + /** + * Location of the previous element (NULL if this is the first) + */ + char *prev; + + /** Size of this element */ + size_t size; +}; + +/** + * Initializes UcxStack structure with memory. + * + * @param stack a pointer to an uninitialized stack structure + * @param space the memory area that shall be managed + * @param size size of the memory area + * @return a new UcxStack structure + */ +void ucx_stack_init(UcxStack *stack, char* space, size_t size); + +/** + * Allocates stack memory. + * + * @param stack a pointer to the stack + * @param n amount of memory to allocate + * @return a pointer to the allocated memory or NULL on stack + * overflow + * @see ucx_allocator_malloc() + */ +void *ucx_stack_malloc(UcxStack *stack, size_t n); + +/** + * Allocates memory with #ucx_stack_malloc() and copies the specified data if + * the allocation was successful. + * + * @param stack a pointer to the stack + * @param n amount of memory to allocate + * @param data a pointer to the data to copy + * @return a pointer to the allocated memory + * @see ucx_stack_malloc + */ +void *ucx_stack_push(UcxStack *stack, size_t n, const void *data); + +/** + * Allocates an array of stack memory + * + * The content of the allocated memory is set to zero. + * + * @param stack a pointer to the stack + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @return a pointer to the allocated memory + * @see ucx_allocator_calloc() + */ +void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize); + +/** + * Allocates memory with #ucx_stack_calloc() and copies the specified data if + * the allocation was successful. + * + * @param stack a pointer to the stack + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @param data a pointer to the data + * @return a pointer to the allocated memory + * @see ucx_stack_calloc + */ +void *ucx_stack_pusharr(UcxStack *stack, + size_t nelem, size_t elsize, const void *data); + +/** + * Reallocates memory on the stack. + * + * Shrinking memory is always safe. Extending memory can be very expensive. + * + * @param stack the stack + * @param ptr a pointer to the memory that shall be reallocated + * @param n the new size of the memory + * @return a pointer to the new location of the memory + * @see ucx_allocator_realloc() + */ +void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n); + +/** + * Frees memory on the stack. + * + * Freeing stack memory behaves in a special way. + * + * If the element, that should be freed, is the top most element of the stack, + * it is removed from the stack. Otherwise it is marked as freed. Marked + * elements are removed, when they become the top most elements of the stack. + * + * @param stack a pointer to the stack + * @param ptr a pointer to the memory that shall be freed + */ +void ucx_stack_free(UcxStack *stack, void *ptr); + + +/** + * Returns the size of the top most element. + * @param stack a pointer to the stack + * @return the size of the top most element + */ +#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\ + (stack)->top - 1)->size : 0) + +/** + * Removes the top most element from the stack and copies the content to + * dest, if specified. + * + * Use #ucx_stack_topsize()# to get the amount of memory that must be available + * at the location of dest. + * + * @param stack a pointer to the stack + * @param dest the location where the contents shall be written to, or + * NULL, if the element shall only be removed. + * @see ucx_stack_free + * @see ucx_stack_popn + */ +#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1) + +/** + * Removes the top most element from the stack and copies the content to + * dest. + * + * This function copies at most n bytes to the destination, but + * the element is always freed as a whole. + * If the element was larger than n, the remaining data is lost. + * + * @param stack a pointer to the stack + * @param dest the location where the contents shall be written to + * @param n copies at most n bytes to dest + * @see ucx_stack_pop + */ +void ucx_stack_popn(UcxStack *stack, void *dest, size_t n); + +/** + * Returns the remaining available memory on the specified stack. + * + * @param stack a pointer to the stack + * @return the remaining available memory + */ +size_t ucx_stack_avail(UcxStack *stack); + +/** + * Checks, if the stack is empty. + * + * @param stack a pointer to the stack + * @return nonzero, if the stack is empty, zero otherwise + */ +#define ucx_stack_empty(stack) (!(stack)->top) + +/** + * Computes a recommended size for the stack memory area. Note, that + * reallocations have not been taken into account, so you might need to reserve + * twice as much memory to allow many reallocations. + * + * @param size the approximate payload + * @param elems the approximate count of element allocations + * @return a recommended size for the stack space based on the information + * provided + */ +#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \ + (elems + 1)) + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STACK_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/string.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/string.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,1201 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Bounded string implementation. + * + * The UCX strings (sstr_t) provide an alternative to C strings. + * The main difference to C strings is, that sstr_t does not + * need to be NULL-terminated. Instead the length is stored + * within the structure. + * + * When using sstr_t, developers must be full aware of what type + * of string (NULL-terminated) or not) they are using, when + * accessing the char* ptr directly. + * + * The UCX string module provides some common string functions, known from + * standard libc, working with sstr_t. + * + * @file string.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STRING_H +#define UCX_STRING_H + +#include "ucx.h" +#include "allocator.h" +#include + +/* + * Use this macro to disable the shortcuts if you experience macro collision. + */ +#ifndef UCX_NO_SSTR_SHORTCUTS +/** + * Shortcut for a sstr_t struct + * or scstr_t struct literal. + */ +#define ST(s) { s, sizeof(s)-1 } + +/** Shortcut for the conversion of a C string to a sstr_t. */ +#define S(s) sstrn(s, sizeof(s)-1) + +/** Shortcut for the conversion of a C string to a scstr_t. */ +#define SC(s) scstrn(s, sizeof(s)-1) +#endif /* UCX_NO_SSTR_SHORTCUTS */ + +/* + * Use this macro to disable the format macros. + */ +#ifndef UCX_NO_SSTR_FORMAT_MACROS +/** Expands a sstr_t or scstr_t to printf arguments. */ +#define SFMT(s) (int) (s).length, (s).ptr + +/** Format specifier for a sstr_t or scstr_t. */ +#define PRIsstr ".*s" +#endif /* UCX_NO_SSTR_FORMAT_MACROS */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The UCX string structure. + */ +typedef struct { + /** A pointer to the string + * (not necessarily NULL-terminated) */ + char *ptr; + /** The length of the string */ + size_t length; +} sstr_t; + +/** + * The UCX string structure for immutable (constant) strings. + */ +typedef struct { + /** A constant pointer to the immutable string + * (not necessarily NULL-terminated) */ + const char *ptr; + /** The length of the string */ + size_t length; +} scstr_t; + +#ifdef __cplusplus +} +#endif + + +#ifdef __cplusplus +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @param str some sstr_t + * @return an immutable (scstr_t) version of the provided string. + */ +inline scstr_t s2scstr(sstr_t s) { + scstr_t c; + c.ptr = s.ptr; + c.length = s.length; + return c; +} + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * This variant is used, when the string is already immutable and no operation + * needs to be performed. + * + * Do not use this function manually. + * + * @param str some scstr_t + * @return the argument itself + */ +inline scstr_t s2scstr(scstr_t str) { + return str; +} + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(s) s2scstr(s) +#else + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * This variant is used, when the string is already immutable and no operation + * needs to be performed. + * + * Do not use this function manually. + * + * @param str some scstr_t + * @return the argument itself + */ +scstr_t ucx_sc2sc(scstr_t str); + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @param str some sstr_t + * @return an immutable (scstr_t) version of the provided string. + */ +scstr_t ucx_ss2sc(sstr_t str); + +#if __STDC_VERSION__ >= 201112L +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str) + +#elif defined(__GNUC__) || defined(__clang__) + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(str) __builtin_choose_expr( \ + __builtin_types_compatible_p(typeof(str), sstr_t), \ + ucx_ss2sc, \ + ucx_sc2sc)(str) + +#elif defined(__sun) + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return the an immutable version of the provided string + */ +#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \ + scstr_t ucx_tmp_var_c; \ + ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\ + ucx_tmp_var_c.length = ucx_tmp_var_str.length;\ + ucx_tmp_var_c; }) +#else /* no generics and no builtins */ + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * + * This internal function (ab)uses the C standard an expects one single + * argument which is then implicitly converted to scstr_t without a warning. + * + * Do not use this function manually. + * + * @return the an immutable version of the provided string + */ +scstr_t ucx_ss2c_s(); + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return the an immutable version of the provided string + */ +#define SCSTR(str) ucx_ss2c_s(str) +#endif /* C11 feature test */ + +#endif /* C++ */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Creates a new sstr_t based on a C string. + * + * The length is implicitly inferred by using a call to strlen(). + * + * Note: the sstr_t will share the specified pointer to the C string. + * If you do want a copy, use sstrdup() on the return value of this function. + * + * If you need to wrap a constant string, use scstr(). + * + * @param cstring the C string to wrap + * @return a new sstr_t containing the C string + * + * @see sstrn() + */ +sstr_t sstr(char *cstring); + +/** + * Creates a new sstr_t of the specified length based on a C string. + * + * Note: the sstr_t will share the specified pointer to the C string. + * If you do want a copy, use sstrdup() on the return value of this function. + * + * If you need to wrap a constant string, use scstrn(). + * + * @param cstring the C string to wrap + * @param length the length of the string + * @return a new sstr_t containing the C string + * + * @see sstr() + * @see S() + */ +sstr_t sstrn(char *cstring, size_t length); + +/** + * Creates a new scstr_t based on a constant C string. + * + * The length is implicitly inferred by using a call to strlen(). + * + * Note: the scstr_t will share the specified pointer to the C string. + * If you do want a copy, use scstrdup() on the return value of this function. + * + * @param cstring the C string to wrap + * @return a new scstr_t containing the C string + * + * @see scstrn() + */ +scstr_t scstr(const char *cstring); + + +/** + * Creates a new scstr_t of the specified length based on a constant C string. + * + * Note: the scstr_t will share the specified pointer to the C string. + * If you do want a copy, use scstrdup() on the return value of this function. * + * + * @param cstring the C string to wrap + * @param length the length of the string + * @return a new scstr_t containing the C string + * + * @see scstr() + */ +scstr_t scstrn(const char *cstring, size_t length); + +/** + * Returns the accumulated length of all specified strings. + * + * Attention: if the count argument is larger than the count of the + * specified strings, the behavior is undefined. + * + * @param count the total number of specified strings + * @param ... all strings + * @return the accumulated length of all strings + */ +size_t scstrnlen(size_t count, ...); + +/** + * Returns the accumulated length of all specified strings. + * + * Attention: if the count argument is larger than the count of the + * specified strings, the behavior is undefined. + * + * @param count the total number of specified strings + * @param ... all strings + * @return the cumulated length of all strings + */ +#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__) + +/** + * Concatenates two or more strings. + * + * The resulting string will be allocated by standard malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + */ +sstr_t scstrcat(size_t count, scstr_t s1, ...); + +/** + * Concatenates two or more strings. + * + * The resulting string will be allocated by standard malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + */ +#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__) + +/** + * Concatenates two or more strings using a UcxAllocator. + * + * The resulting string must be freed by the allocators free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param alloc the allocator to use + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + * + * @see scstrcat() + */ +sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...); + +/** + * Concatenates two or more strings using a UcxAllocator. + * + * The resulting string must be freed by the allocators free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param alloc the allocator to use + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + * + * @see sstrcat() + */ +#define sstrcat_a(alloc, count, s1, ...) \ + scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__) + +/** + * Returns a substring starting at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use sstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @see sstrsubsl() + * @see sstrchr() + */ +sstr_t sstrsubs(sstr_t string, size_t start); + +/** + * Returns a substring with the given length starting at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use sstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the substring + * @return a substring of string starting at start + * with a maximum length of length + * + * @see sstrsubs() + * @see sstrchr() + */ +sstr_t sstrsubsl(sstr_t string, size_t start, size_t length); + +/** + * Returns a substring of an immutable string starting at the specified + * location. + * + * Attention: the new string references the same memory area as the +* input string and is NOT required to be NULL-terminated. + * Use scstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @see scstrsubsl() + * @see scstrchr() + */ +scstr_t scstrsubs(scstr_t string, size_t start); + +/** + * Returns a substring of an immutable string with a maximum length starting + * at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use scstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the substring + * @return a substring of string starting at start + * with a maximum length of length + * + * @see scstrsubs() + * @see scstrchr() + */ +scstr_t scstrsubsl(scstr_t string, size_t start, size_t length); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of chr + * + * @see sstrsubs() + */ +sstr_t sstrchr(sstr_t string, int chr); + +/** + * Returns a substring starting at the location of the last occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of chr + * + * @see sstrsubs() + */ +sstr_t sstrrchr(sstr_t string, int chr); + +/** + * Returns an immutable substring starting at the location of the first + * occurrence of the specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of chr + * + * @see scstrsubs() + */ +scstr_t scstrchr(scstr_t string, int chr); + +/** + * Returns an immutable substring starting at the location of the last + * occurrence of the specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of chr + * + * @see scstrsubs() + */ +scstr_t scstrrchr(scstr_t string, int chr); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +sstr_t scstrsstr(sstr_t string, scstr_t match); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +#define sstrstr(string, match) scstrsstr(string, SCSTR(match)) + +/** + * Returns an immutable substring starting at the location of the + * first occurrence of the specified immutable string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +scstr_t scstrscstr(scstr_t string, scstr_t match); + +/** + * Returns an immutable substring starting at the location of the + * first occurrence of the specified immutable string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +#define sstrscstr(string, match) scstrscstr(string, SCSTR(match)) + +/** + * Splits a string into parts by using a delimiter string. + * + * This function will return NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count is used as input and determines + * the maximum size of the resulting array, i.e. the maximum count of splits to + * perform + 1. + * + * The integer referenced by count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * If the string starts with the delimiter, the first item of the resulting + * array will be an empty string. + * + * If the string ends with the delimiter and the maximum list size is not + * exceeded, the last array item will be an empty string. + * In case the list size would be exceeded, the last array item will be the + * remaining string after the last split, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). Use scstrsplit_a() with + * an allocator to managed memory, to avoid this. + * + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see scstrsplit_a() + */ +sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count); + +/** + * Splits a string into parts by using a delimiter string. + * + * This function will return NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count is used as input and determines + * the maximum size of the resulting array, i.e. the maximum count of splits to + * perform + 1. + * + * The integer referenced by count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * If the string starts with the delimiter, the first item of the resulting + * array will be an empty string. + * + * If the string ends with the delimiter and the maximum list size is not + * exceeded, the last array item will be an empty string. + * In case the list size would be exceeded, the last array item will be the + * remaining string after the last split, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). Use sstrsplit_a() with + * an allocator to managed memory, to avoid this. + * + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see sstrsplit_a() + */ +#define sstrsplit(string, delim, count) \ + scstrsplit(SCSTR(string), SCSTR(delim), count) + +/** + * Performing scstrsplit() using a UcxAllocator. + * + * Read the description of scstrsplit() for details. + * + * The memory for the sstr_t.ptr pointers of the array items and the memory for + * the sstr_t array itself are allocated by using the UcxAllocator.malloc() + * function. + * + * @param allocator the UcxAllocator used for allocating memory + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see scstrsplit() + */ +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim, + ssize_t *count); + +/** + * Performing sstrsplit() using a UcxAllocator. + * + * Read the description of sstrsplit() for details. + * + * The memory for the sstr_t.ptr pointers of the array items and the memory for + * the sstr_t array itself are allocated by using the UcxAllocator.malloc() + * function. + * + * @param allocator the UcxAllocator used for allocating memory + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see sstrsplit() + */ +#define sstrsplit_a(allocator, string, delim, count) \ + scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count) + +/** + * Compares two UCX strings with standard memcmp(). + * + * At first it compares the scstr_t.length attribute of the two strings. The + * memcmp() function is called, if and only if the lengths match. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of + * memcmp() otherwise (i.e. 0 if the strings match) + */ +int scstrcmp(scstr_t s1, scstr_t s2); + +/** + * Compares two UCX strings with standard memcmp(). + * + * At first it compares the sstr_t.length attribute of the two strings. The + * memcmp() function is called, if and only if the lengths match. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of + * memcmp() otherwise (i.e. 0 if the strings match) + */ +#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2)) + +/** + * Compares two UCX strings ignoring the case. + * + * At first it compares the scstr_t.length attribute of the two strings. If and + * only if the lengths match, both strings are compared char by char ignoring + * the case. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of the platform + * specific string comparison function ignoring the case. + */ +int scstrcasecmp(scstr_t s1, scstr_t s2); + +/** + * Compares two UCX strings ignoring the case. + * + * At first it compares the sstr_t.length attribute of the two strings. If and + * only if the lengths match, both strings are compared char by char ignoring + * the case. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of the platform + * specific string comparison function ignoring the case. + */ +#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2)) + +/** + * Creates a duplicate of the specified string. + * + * The new sstr_t will contain a copy allocated by standard + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated and mutable, regardless of the argument. + * + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup_a() + */ +sstr_t scstrdup(scstr_t string); + +/** + * Creates a duplicate of the specified string. + * + * The new sstr_t will contain a copy allocated by standard + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated, regardless of the argument. + * + * @param string the string to duplicate + * @return a duplicate of the string + * @see sstrdup_a() + */ +#define sstrdup(string) scstrdup(SCSTR(string)) + +/** + * Creates a duplicate of the specified string using a UcxAllocator. + * + * The new sstr_t will contain a copy allocated by the allocators + * UcxAllocator.malloc() function. So it is implementation depended, whether the + * returned sstr_t.ptr pointer must be passed to the allocators + * UcxAllocator.free() function manually. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated and mutable, regardless of the argument. + * + * @param allocator a valid instance of a UcxAllocator + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup() + */ +sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string); + +/** + * Creates a duplicate of the specified string using a UcxAllocator. + * + * The new sstr_t will contain a copy allocated by the allocators + * UcxAllocator.malloc() function. So it is implementation depended, whether the + * returned sstr_t.ptr pointer must be passed to the allocators + * UcxAllocator.free() function manually. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated, regardless of the argument. + * + * @param allocator a valid instance of a UcxAllocator + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup() + */ +#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string)) + + +/** + * Omits leading and trailing spaces. + * + * This function returns a new sstr_t containing a trimmed version of the + * specified string. + * + * Note: the new sstr_t references the same memory, thus you + * MUST NOT pass the sstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = sstrtrim(mystr); as you lose the reference to the + * source string. Assignments of this type are only permitted, if the + * sstr_t.ptr of the source string does not need to be freed or if another + * reference to the source string exists. + * + * @param string the string that shall be trimmed + * @return a new sstr_t containing the trimmed string + */ +sstr_t sstrtrim(sstr_t string); + +/** + * Omits leading and trailing spaces. + * + * This function returns a new scstr_t containing a trimmed version of the + * specified string. + * + * Note: the new scstr_t references the same memory, thus you + * MUST NOT pass the scstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = scstrtrim(mystr); as you lose the reference to the + * source string. Assignments of this type are only permitted, if the + * scstr_t.ptr of the source string does not need to be freed or if another + * reference to the source string exists. + * + * @param string the string that shall be trimmed + * @return a new scstr_t containing the trimmed string + */ +scstr_t scstrtrim(scstr_t string); + +/** + * Checks, if a string has a specific prefix. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +int scstrprefix(scstr_t string, scstr_t prefix); + +/** + * Checks, if a string has a specific prefix. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix)) + +/** + * Checks, if a string has a specific suffix. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +int scstrsuffix(scstr_t string, scstr_t suffix); + +/** + * Checks, if a string has a specific suffix. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(suffix)) + +/** + * Checks, if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +int scstrcaseprefix(scstr_t string, scstr_t prefix); + +/** + * Checks, if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +#define sstrcaseprefix(string, prefix) \ + scstrcaseprefix(SCSTR(string), SCSTR(prefix)) + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +int scstrcasesuffix(scstr_t string, scstr_t suffix); + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +#define sstrcasesuffix(string, suffix) \ + scstrcasesuffix(SCSTR(string), SCSTR(suffix)) + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup()). + * + * @param string the input string + * @return the resulting lower case string + * @see scstrdup() + */ +sstr_t scstrlower(scstr_t string); + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup()). + * + * @param string the input string + * @return the resulting lower case string + */ +#define sstrlower(string) scstrlower(SCSTR(string)) + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting lower case string + * @see scstrdup_a() + */ +sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string); + + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting lower case string + */ +#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string)) + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup()). + * + * @param string the input string + * @return the resulting upper case string + * @see scstrdup() + */ +sstr_t scstrupper(scstr_t string); + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup()). + * + * @param string the input string + * @return the resulting upper case string + */ +#define sstrupper(string) scstrupper(SCSTR(string)) + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting upper case string + * @see scstrdup_a() + */ +sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string); + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting upper case string + */ +#define sstrupper_a(allocator, string) scstrupper_a(allocator, string) + + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, + scstr_t pattern, scstr_t replacement, size_t replmax); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +sstr_t scstrreplacen(scstr_t str, scstr_t pattern, + scstr_t replacement, size_t replmax); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \ + scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \ + SCSTR(replacement), replmax) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +#define sstrreplacen(str, pattern, replacement, replmax) \ + scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @return the resulting string after applying the replacements + */ +#define sstrreplace_a(allocator, str, pattern, replacement) \ + scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \ + SCSTR(replacement), SIZE_MAX) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @return the resulting string after applying the replacements + */ +#define sstrreplace(str, pattern, replacement) \ + scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STRING_H */ diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/test.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/test.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + *
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * 
+ * + * **** IN SOURCE FILE: **** + *
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with UCX_TEST_ASSERT()
+ * }
+ * 
+ * UCX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #UCX_TEST_BEGIN
+ *   // tests with UCX_TEST_ASSERT() and/or
+ *   // calls with UCX_TEST_CALL_SUBROUTINE() here
+ *   #UCX_TEST_END
+ *   // cleanup of memory here
+ * }
+ * 
+ * + * Note: if a test fails, a longjump is performed + * back to the #UCX_TEST_BEGIN macro! + * + * Attention: Do not call own functions within a test, that use + * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE(). + * + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_TEST_H +#define UCX_TEST_H + +#include "ucx.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ + +/** + * Alias for the __func__ preprocessor macro. + * Some compilers use __func__ and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * __func__. + */ +#define __FUNCTION__ __func__ +#endif + +/** Type for the UcxTestSuite. */ +typedef struct UcxTestSuite UcxTestSuite; + +/** Pointer to a test function. */ +typedef void(*UcxTest)(UcxTestSuite*,FILE*); + +/** Type for the internal list of test cases. */ +typedef struct UcxTestList UcxTestList; + +/** Structure for the internal list of test cases. */ +struct UcxTestList { + + /** Test case. */ + UcxTest test; + + /** Pointer to the next list element. */ + UcxTestList *next; +}; + +/** + * A test suite containing multiple test cases. + */ +struct UcxTestSuite { + + /** The number of successful tests after the suite has been run. */ + unsigned int success; + + /** The number of failed tests after the suite has been run. */ + unsigned int failure; + + /** + * Internal list of test cases. + * Use ucx_test_register() to add tests to this list. + */ + UcxTestList *tests; +}; + +/** + * Creates a new test suite. + * @return a new test suite + */ +UcxTestSuite* ucx_test_suite_new(); + +/** + * Destroys a test suite. + * @param suite the test suite to destroy + */ +void ucx_test_suite_free(UcxTestSuite* suite); + +/** + * Registers a test function with the specified test suite. + * + * @param suite the suite, the test function shall be added to + * @param test the test function to register + * @return EXIT_SUCCESS on success or + * EXIT_FAILURE on failure + */ +int ucx_test_register(UcxTestSuite* suite, UcxTest test); + +/** + * Runs a test suite and writes the test log to the specified stream. + * @param suite the test suite to run + * @param outstream the stream the log shall be written to + */ +void ucx_test_run(UcxTestSuite* suite, FILE* outstream); + +/** + * Macro for a #UcxTest function header. + * + * Use this macro to declare and/or define a #UcxTest function. + * + * @param name the name of the test function + */ +#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_) + +/** + * Marks the begin of a test. + * Note: Any UCX_TEST_ASSERT() calls must be performed after + * #UCX_TEST_BEGIN. + * + * @see #UCX_TEST_END + */ +#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\ + fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ + fwrite("... ", 1, 4, _output_);\ + jmp_buf _env_; \ + if (!setjmp(_env_)) { + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition the condition to check + * @param message the message that shall be printed out on failure + */ +#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \ + fwrite(message".\n", 1, 2+strlen(message), _output_); \ + _suite_->failure++; \ + longjmp(_env_, 1);\ + } + +/** + * Macro for a test subroutine function header. + * + * Use this to declare and/or define a subroutine that can be called by using + * UCX_TEST_CALL_SUBROUTINE(). + * + * @param name the name of the subroutine + * @param ... the parameter list + * + * @see UCX_TEST_CALL_SUBROUTINE() + */ +#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\ + FILE *_output_, jmp_buf _env_, __VA_ARGS__) + +/** + * Macro for calling a test subroutine. + * + * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this + * macro. + * + * Note: You may only call subroutines within a #UCX_TEST_BEGIN- + * #UCX_TEST_END-block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see UCX_TEST_SUBROUTINE() + */ +#define UCX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_env_,__VA_ARGS__); + +/** + * Marks the end of a test. + * Note: Any UCX_TEST_ASSERT() calls must be performed before + * #UCX_TEST_END. + * + * @see #UCX_TEST_BEGIN + */ +#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;} + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/ucx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/ucx.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,204 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Main UCX Header providing most common definitions. + * + * @file ucx.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_H +#define UCX_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 2 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 1 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +#include +#include + +#ifdef _WIN32 +#if !(defined __ssize_t_defined || defined _SSIZE_T_) +#include +typedef SSIZE_T ssize_t; +#define __ssize_t_defined +#define _SSIZE_T_ +#endif /* __ssize_t_defined and _SSIZE_T */ +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * A function pointer to a destructor function. + * @see ucx_mempool_setdestr() + * @see ucx_mempool_regdestr() + */ +typedef void(*ucx_destructor)(void*); + +/** + * Function pointer to a compare function. + * + * The compare function shall take three arguments: the two values that shall be + * compared and optional additional data. + * The function shall then return -1 if the first argument is less than the + * second argument, 1 if the first argument is greater than the second argument + * and 0 if both arguments are equal. If the third argument is + * NULL, it shall be ignored. + */ +typedef int(*cmp_func)(const void*,const void*,void*); + +/** + * Function pointer to a distance function. + * + * The distance function shall take three arguments: the two values for which + * the distance shall be computed and optional additional data. + * The function shall then return the signed distance as integer value. + */ +typedef intmax_t(*distance_func)(const void*,const void*,void*); + +/** + * Function pointer to a copy function. + * + * The copy function shall create a copy of the first argument and may use + * additional data provided by the second argument. If the second argument is + * NULL, it shall be ignored. + + * Attention: if pointers returned by functions of this type may be + * passed to free() depends on the implementation of the + * respective copy_func. + */ +typedef void*(*copy_func)(const void*,void*); + +/** + * Function pointer to a write function. + * + * The signature of the write function shall be compatible to the signature + * of standard fwrite, though it may use arbitrary data types for + * source and destination. + * + * The arguments shall contain (in ascending order): a pointer to the source, + * the length of one element, the element count and a pointer to the + * destination. + */ +typedef size_t(*write_func)(const void*, size_t, size_t, void*); + +/** + * Function pointer to a read function. + * + * The signature of the read function shall be compatible to the signature + * of standard fread, though it may use arbitrary data types for + * source and destination. + * + * The arguments shall contain (in ascending order): a pointer to the + * destination, the length of one element, the element count and a pointer to + * the source. + */ +typedef size_t(*read_func)(void*, size_t, size_t, void*); + + + +#if __GNUC__ >= 5 || defined(__clang__) +#define UCX_MUL_BUILTIN + +#if __WORDSIZE == 32 +/** + * Alias for __builtin_umul_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result) +#else /* __WORDSIZE != 32 */ +/** + * Alias for __builtin_umull_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result) +#endif /* __WORDSIZE */ + +#else /* no GNUC or clang bultin */ + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result) + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * This is a custom implementation in case there is no compiler builtin + * available. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t where the result should be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +int ucx_szmul_impl(size_t a, size_t b, size_t *result); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/ucx/utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ucx/ucx/utils.h Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,508 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file utils.h + * + * Compare, copy and printf functions. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_UTILS_H +#define UCX_UTILS_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy(). + */ +#define UCX_STREAM_COPY_BUFSIZE 4096 + +/** + * Copies a string. + * @param s the string to copy + * @param data omitted + * @return a pointer to a copy of s1 that can be passed to free(void*) + */ +void *ucx_strcpy(const void *s, void *data); + +/** + * Copies a memory area. + * @param m a pointer to the memory area + * @param n a pointer to the size_t containing the size of the memory area + * @return a pointer to a copy of the specified memory area that can + * be passed to free(void*) + */ +void *ucx_memcpy(const void *m, void *n); + + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, this is the size of the buffer that shall be + * implicitly created + * @param n the maximum number of bytes that shall be copied + * @return the total number of bytes copied + */ +size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc, + char* buf, size_t bufsize, size_t n); + +/** + * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @return total number of bytes copied + * + * @see #UCX_STREAM_COPY_BUFSIZE + */ +#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1) + +/** + * Shorthand for ucx_stream_bncopy using a default copy buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n maximum number of bytes that shall be copied + * @return total number of bytes copied + */ +#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + NULL, UCX_STREAM_COPY_BUFSIZE, n) + +/** + * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, this is the size of the buffer that shall be + * implicitly created + * @return total number of bytes copied + */ +#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + buf, bufsize, (size_t)-1) + +/** + * Wraps the strcmp function. + * @param s1 string one + * @param s2 string two + * @param data omitted + * @return the result of strcmp(s1, s2) + */ +int ucx_cmp_str(const void *s1, const void *s2, void *data); + +/** + * Wraps the strncmp function. + * @param s1 string one + * @param s2 string two + * @param n a pointer to the size_t containing the third strncmp parameter + * @return the result of strncmp(s1, s2, *n) + */ +int ucx_cmp_strn(const void *s1, const void *s2, void *n); + +/** + * Wraps the sstrcmp function. + * @param s1 sstr one + * @param s2 sstr two + * @param data ignored + * @return the result of sstrcmp(s1, s2) + */ +int ucx_cmp_sstr(const void *s1, const void *s2, void *data); + +/** + * Compares two integers of type int. + * @param i1 pointer to integer one + * @param i2 pointer to integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type long int. + * @param i1 pointer to long integer one + * @param i2 pointer to long integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_longint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type long long. + * @param i1 pointer to long long one + * @param i2 pointer to long long two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_longlong(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int16_t. + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int16(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int32_t. + * @param i1 pointer to int32_t one + * @param i2 pointer to int32_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int32(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int64_t. + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int64(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned int. + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned long int. + * @param i1 pointer to unsigned long integer one + * @param i2 pointer to unsigned long integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_ulongint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned long long. + * @param i1 pointer to unsigned long long one + * @param i2 pointer to unsigned long long two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint16_t. + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint16(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint32_t. + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint32(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint64_t. + * @param i1 pointer to uint64_t one + * @param i2 pointer to uint64_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint64(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int. + * @param i1 pointer to integer one + * @param i2 pointer to integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type long int. + * @param i1 pointer to long integer one + * @param i2 pointer to long integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type long long. + * @param i1 pointer to long long one + * @param i2 pointer to long long two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int16_t. + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int32_t. + * @param i1 pointer to int32_t one + * @param i2 pointer to int32_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int64_t. + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned int. + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned long int. + * @param i1 pointer to unsigned long integer one + * @param i2 pointer to unsigned long integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned long long. + * @param i1 pointer to unsigned long long one + * @param i2 pointer to unsigned long long two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint16_t. + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint32_t. + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint64_t. + * @param i1 pointer to uint64_t one + * @param i2 pointer to uint64_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data); + +/** + * Compares two real numbers of type float. + * @param f1 pointer to float one + * @param f2 pointer to float two + * @param data if provided: a pointer to precision (default: 1e-6f) + * @return -1, if *f1 is less than *f2, 0 if both are equal, + * 1 if *f1 is greater than *f2 + */ + +int ucx_cmp_float(const void *f1, const void *f2, void *data); + +/** + * Compares two real numbers of type double. + * @param d1 pointer to double one + * @param d2 pointer to double two + * @param data if provided: a pointer to precision (default: 1e-14) + * @return -1, if *d1 is less than *d2, 0 if both are equal, + * 1 if *d1 is greater than *d2 + */ +int ucx_cmp_double(const void *d1, const void *d2, void *data); + +/** + * Compares two pointers. + * @param ptr1 pointer one + * @param ptr2 pointer two + * @param data omitted + * @return -1 if ptr1 is less than ptr2, 0 if both are equal, + * 1 if ptr1 is greater than ptr2 + */ +int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data); + +/** + * Compares two memory areas. + * @param ptr1 pointer one + * @param ptr2 pointer two + * @param n a pointer to the size_t containing the third parameter for memcmp + * @return the result of memcmp(ptr1, ptr2, *n) + */ +int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n); + +/** + * A printf() like function which writes the output to a stream by + * using a write_func(). + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ... additional arguments + * @return the total number of bytes written + */ +int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...); + +/** + * va_list version of ucx_fprintf(). + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ap argument list + * @return the total number of bytes written + * @see ucx_fprintf() + */ +int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap); + +/** + * A printf() like function which allocates space for a sstr_t + * the result is written to. + * + * Attention: The sstr_t data is allocated with the allocators + * ucx_allocator_malloc() function. So it is implementation dependent, if + * the returned sstr_t.ptr pointer must be passed to the allocators + * ucx_allocator_free() function manually. + * + * Note: The sstr_t.ptr of the return value will always be + * NULL-terminated. + * + * @param allocator the UcxAllocator used for allocating the result sstr_t + * @param fmt format string + * @param ... additional arguments + * @return a sstr_t containing the formatted string + */ +sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...); + +/** + * va_list version of ucx_asprintf(). + * + * @param allocator the UcxAllocator used for allocating the result sstr_t + * @param fmt format string + * @param ap argument list + * @return a sstr_t containing the formatted string + * @see ucx_asprintf() + */ +sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap); + +/** Shortcut for ucx_asprintf() with default allocator. */ +#define ucx_sprintf(...) \ + ucx_asprintf(ucx_default_allocator(), __VA_ARGS__) + +/** + * A printf() like function which writes the output to a + * UcxBuffer. + * + * @param buffer the buffer the data is written to + * @param ... format string and additional arguments + * @return the total number of bytes written + * @see ucx_fprintf() + */ +#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \ + (write_func)ucx_buffer_write, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_UTILS_H */ + diff -r 21274e5950af -r a1f4cb076d2f src/ucx/utils.c --- a/src/ucx/utils.c Tue Aug 13 22:14:32 2019 +0200 +++ b/src/ucx/utils.c Sat Sep 24 16:26:10 2022 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2016 Olaf Wintermann. All rights reserved. + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,22 +26,23 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "utils.h" +#include "ucx/utils.h" + #include #include #include #include /* COPY FUCNTIONS */ -void* ucx_strcpy(void* s, void* data) { - char *str = (char*) s; +void* ucx_strcpy(const void* s, void* data) { + const char *str = (const char*) s; size_t n = 1+strlen(str); char *cpy = (char*) malloc(n); memcpy(cpy, str, n); return cpy; } -void* ucx_memcpy(void* m, void* n) { +void* ucx_memcpy(const void* m, void* n) { size_t k = *((size_t*)n); void *cpy = malloc(k); memcpy(cpy, m, k); @@ -87,17 +88,103 @@ /* COMPARE FUNCTIONS */ -int ucx_strcmp(void *s1, void *s2, void *data) { - return strcmp((char*)s1, (char*)s2); +int ucx_cmp_str(const void *s1, const void *s2, void *data) { + return strcmp((const char*)s1, (const char*)s2); +} + +int ucx_cmp_strn(const void *s1, const void *s2, void *n) { + return strncmp((const char*)s1, (const char*)s2, *((size_t*) n)); +} + +int ucx_cmp_sstr(const void *s1, const void *s2, void *data) { + sstr_t a = *(const sstr_t*) s1; + sstr_t b = *(const sstr_t*) s2; + return sstrcmp(a, b); +} + +int ucx_cmp_int(const void *i1, const void *i2, void *data) { + int a = *((const int*) i1); + int b = *((const int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longint(const void *i1, const void *i2, void *data) { + long int a = *((const long int*) i1); + long int b = *((const long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longlong(const void *i1, const void *i2, void *data) { + long long a = *((const long long*) i1); + long long b = *((const long long*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } } -int ucx_strncmp(void *s1, void *s2, void *n) { - return strncmp((char*)s1, (char*)s2, *((size_t*) n)); +int ucx_cmp_int16(const void *i1, const void *i2, void *data) { + int16_t a = *((const int16_t*) i1); + int16_t b = *((const int16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int32(const void *i1, const void *i2, void *data) { + int32_t a = *((const int32_t*) i1); + int32_t b = *((const int32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } } -int ucx_intcmp(void *i1, void *i2, void *data) { - int a = *((int*) i1); - int b = *((int*) i2); +int ucx_cmp_int64(const void *i1, const void *i2, void *data) { + int64_t a = *((const int64_t*) i1); + int64_t b = *((const int64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint(const void *i1, const void *i2, void *data) { + unsigned int a = *((const unsigned int*) i1); + unsigned int b = *((const unsigned int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) { + unsigned long int a = *((const unsigned long int*) i1); + unsigned long int b = *((const unsigned long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) { + unsigned long long a = *((const unsigned long long*) i1); + unsigned long long b = *((const unsigned long long*) i2); if (a == b) { return 0; } else { @@ -105,9 +192,111 @@ } } -int ucx_floatcmp(void *f1, void *f2, void *epsilon) { - float a = *((float*) f1); - float b = *((float*) f2); +int ucx_cmp_uint16(const void *i1, const void *i2, void *data) { + uint16_t a = *((const uint16_t*) i1); + uint16_t b = *((const uint16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint32(const void *i1, const void *i2, void *data) { + uint32_t a = *((const uint32_t*) i1); + uint32_t b = *((const uint32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint64(const void *i1, const void *i2, void *data) { + uint64_t a = *((const uint64_t*) i1); + uint64_t b = *((const uint64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int*) i1); + intmax_t b = *((const int*) i2); + return a - b; +} + +intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long int*) i1); + intmax_t b = *((const long int*) i2); + return a - b; +} + +intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long long*) i1); + intmax_t b = *((const long long*) i2); + return a - b; +} + +intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int16_t*) i1); + intmax_t b = *((const int16_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int32_t*) i1); + intmax_t b = *((const int32_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int64_t*) i1); + intmax_t b = *((const int64_t*) i2); + return a - b; +} + +intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned int*) i1); + uintmax_t b = *((const unsigned int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long int*) i1); + uintmax_t b = *((const unsigned long int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long long*) i1); + uintmax_t b = *((const unsigned long long*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint16_t*) i1); + uintmax_t b = *((const uint16_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint32_t*) i1); + uintmax_t b = *((const uint32_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint64_t*) i1); + uintmax_t b = *((const uint64_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) { + float a = *((const float*) f1); + float b = *((const float*) f2); float e = !epsilon ? 1e-6f : *((float*)epsilon); if (fabsf(a - b) < e) { return 0; @@ -116,9 +305,9 @@ } } -int ucx_doublecmp(void *d1, void *d2, void *epsilon) { - double a = *((float*) d1); - double b = *((float*) d2); +int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) { + double a = *((const double*) d1); + double b = *((const double*) d2); double e = !epsilon ? 1e-14 : *((double*)epsilon); if (fabs(a - b) < e) { return 0; @@ -127,9 +316,9 @@ } } -int ucx_ptrcmp(void *ptr1, void *ptr2, void *data) { - intptr_t p1 = (intptr_t) ptr1; - intptr_t p2 = (intptr_t) ptr2; +int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) { + const intptr_t p1 = (const intptr_t) ptr1; + const intptr_t p2 = (const intptr_t) ptr2; if (p1 == p2) { return 0; } else { @@ -137,7 +326,7 @@ } } -int ucx_memcmp(void *ptr1, void *ptr2, void *n) { +int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) { return memcmp(ptr1, ptr2, *((size_t*)n)); } diff -r 21274e5950af -r a1f4cb076d2f src/ucx/utils.h --- a/src/ucx/utils.h Tue Aug 13 22:14:32 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2016 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file utils.h - * - * Compare, copy and printf functions. - * - * @author Mike Becker - * @author Olaf Wintermann - */ - -#ifndef UCX_UTILS_H -#define UCX_UTILS_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "ucx.h" -#include "string.h" -#include "allocator.h" -#include -#include -#include - -/** - * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy(). - */ -#define UCX_STREAM_COPY_BUFSIZE 4096 - -/** - * Copies a string. - * @param s the string to copy - * @param data omitted - * @return a pointer to a copy of s1 that can be passed to free(void*) - */ -void *ucx_strcpy(void *s, void *data); - -/** - * Copies a memory area. - * @param m a pointer to the memory area - * @param n a pointer to the size_t containing the size of the memory area - * @return a pointer to a copy of the specified memory area that can - * be passed to free(void*) - */ -void *ucx_memcpy(void *m, void *n); - - -/** - * Reads data from a stream and writes it to another stream. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if NULL was - * provided for buf, this is the size of the buffer that shall be - * implicitly created - * @param n the maximum number of bytes that shall be copied - * @return the total number of bytes copied - */ -size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc, - char* buf, size_t bufsize, size_t n); - -/** - * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @return total number of bytes copied - * - * @see #UCX_STREAM_COPY_BUFSIZE - */ -#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\ - src, dest, (read_func)rfnc, (write_func)wfnc, \ - NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1) - -/** - * Shorthand for ucx_stream_bncopy using a default copy buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param n maximum number of bytes that shall be copied - * @return total number of bytes copied - */ -#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\ - src, dest, (read_func)rfnc, (write_func)wfnc, \ - NULL, UCX_STREAM_COPY_BUFSIZE, n) - -/** - * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if NULL was - * provided for buf, this is the size of the buffer that shall be - * implicitly created - * @return total number of bytes copied - */ -#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\ - src, dest, (read_func)rfnc, (write_func)wfnc, \ - buf, bufsize, (size_t)-1) - -/** - * Wraps the strcmp function. - * @param s1 string one - * @param s2 string two - * @param data omitted - * @return the result of strcmp(s1, s2) - */ -int ucx_strcmp(void *s1, void *s2, void *data); - -/** - * Wraps the strncmp function. - * @param s1 string one - * @param s2 string two - * @param n a pointer to the size_t containing the third strncmp parameter - * @return the result of strncmp(s1, s2, *n) - */ -int ucx_strncmp(void *s1, void *s2, void *n); - -/** - * Compares two integers of type int. - * @param i1 pointer to integer one - * @param i2 pointer to integer two - * @param data omitted - * @return -1, if *i1 is less than *i2, 0 if both are equal, - * 1 if *i1 is greater than *i2 - */ -int ucx_intcmp(void *i1, void *i2, void *data); - -/** - * Compares two real numbers of type float. - * @param f1 pointer to float one - * @param f2 pointer to float two - * @param data if provided: a pointer to precision (default: 1e-6f) - * @return -1, if *f1 is less than *f2, 0 if both are equal, - * 1 if *f1 is greater than *f2 - */ - -int ucx_floatcmp(void *f1, void *f2, void *data); - -/** - * Compares two real numbers of type double. - * @param d1 pointer to double one - * @param d2 pointer to double two - * @param data if provided: a pointer to precision (default: 1e-14) - * @return -1, if *d1 is less than *d2, 0 if both are equal, - * 1 if *d1 is greater than *d2 - */ -int ucx_doublecmp(void *d1, void *d2, void *data); - -/** - * Compares two pointers. - * @param ptr1 pointer one - * @param ptr2 pointer two - * @param data omitted - * @return -1 if ptr1 is less than ptr2, 0 if both are equal, - * 1 if ptr1 is greater than ptr2 - */ -int ucx_ptrcmp(void *ptr1, void *ptr2, void *data); - -/** - * Compares two memory areas. - * @param ptr1 pointer one - * @param ptr2 pointer two - * @param n a pointer to the size_t containing the third parameter for memcmp - * @return the result of memcmp(ptr1, ptr2, *n) - */ -int ucx_memcmp(void *ptr1, void *ptr2, void *n); - -/** - * A printf() like function which writes the output to a stream by - * using a write_func(). - * @param stream the stream the data is written to - * @param wfc the write function - * @param fmt format string - * @param ... additional arguments - * @return the total number of bytes written - */ -int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...); - -/** - * va_list version of ucx_fprintf(). - * @param stream the stream the data is written to - * @param wfc the write function - * @param fmt format string - * @param ap argument list - * @return the total number of bytes written - * @see ucx_fprintf() - */ -int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap); - -/** - * A printf() like function which allocates space for a sstr_t - * the result is written to. - * - * Attention: The sstr_t data is allocated with the allocators - * ucx_allocator_malloc() function. So it is implementation dependent, if - * the returned sstr_t.ptr pointer must be passed to the allocators - * ucx_allocator_free() function manually. - * - * Note: The sstr_t.ptr of the return value will always be - * NULL-terminated. - * - * @param allocator the UcxAllocator used for allocating the result sstr_t - * @param fmt format string - * @param ... additional arguments - * @return a sstr_t containing the formatted string - */ -sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...); - -/** - * va_list version of ucx_asprintf(). - * - * @param allocator the UcxAllocator used for allocating the result sstr_t - * @param fmt format string - * @param ap argument list - * @return a sstr_t containing the formatted string - * @see ucx_asprintf() - */ -sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap); - -/** Shortcut for ucx_asprintf() with default allocator. */ -#define ucx_sprintf(...) \ - ucx_asprintf(ucx_default_allocator(), __VA_ARGS__) - -/** - * A printf() like function which writes the output to a - * UcxBuffer. - * - * @param buffer the buffer the data is written to - * @param ... format string and additional arguments - * @return the total number of bytes written - * @see ucx_fprintf() - */ -#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \ - (write_func)ucx_buffer_write, __VA_ARGS__) - -#ifdef __cplusplus -} -#endif - -#endif /* UCX_UTILS_H */ - diff -r 21274e5950af -r a1f4cb076d2f templates/config/init.conf --- a/templates/config/init.conf Tue Aug 13 22:14:32 2019 +0200 +++ b/templates/config/init.conf Sat Sep 24 16:26:10 2022 +0200 @@ -3,4 +3,6 @@ # Init fn="admin-init" +Init fn="webdav-init" + diff -r 21274e5950af -r a1f4cb076d2f templates/config/server.template --- a/templates/config/server.template Tue Aug 13 22:14:32 2019 +0200 +++ b/templates/config/server.template Sat Sep 24 16:26:10 2022 +0200 @@ -2,46 +2,54 @@ # server.conf # - +Runtime +{ Temp /tmp/webserver-rw6pgl8b/ User webservd MimeFile mime.types - +} - +LogFile +{ File logs/errors Level INFO - +} - +AccessLog +{ File logs/access - +} - +EventHandler +{ Name default Threads 1 Default true - +} - +Threadpool +{ Name default MinThreads 4 MaxThreads 32 - +} - +Listener +{ Name http-listener-1 Port 9090 DefaultVS %%WS_HOST%% - +} - +Listener +{ Name http-listener-2 Port 9091 DefaultVS %%WS_HOST%% - +} - +VirtualServer +{ Name %%WS_HOST%% Host %%WS_HOST%% Listener http-listener-1 @@ -50,6 +58,9 @@ ACLFile acl.conf DAVFile dav.conf DocRoot docs/ - +} + + +