--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/wsgidav.c Sat Jan 24 13:56:19 2026 +0100 @@ -0,0 +1,306 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2026 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 "wsgidav.h" + +#include <stdio.h> +#include <stdlib.h> + +//ifndef _WIN32 + +#include <unistd.h> +#include <sys/fcntl.h> + +#include <spawn.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include <cx/string.h> +#include <cx/buffer.h> +#include <cx/streams.h> +#include <cx/printf.h> + + +// -------------------------- Process Utils -------------------------------- + +typedef struct { + pid_t pid; + int in; + int out; + int err; +} Process; + +static Process process_spawn(const char *exec, char **args) { + Process proc = { 0 }; + + int pin[2]; + int pout[2]; + int perr[2]; + + // child process stdin/stdout pipes + if(pipe(pin)) { + perror("pipe"); + return proc; + } + if(pipe(pout)) { + perror("pipe"); + close(pin[0]); + close(pin[1]); + } + if(pipe(perr)) { + perror("pipe"); + close(pin[0]); + close(pin[1]); + close(pout[0]); + close(pout[1]); + } + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_adddup2(&actions, pin[0], 0); + posix_spawn_file_actions_adddup2(&actions, pout[1], 1); + posix_spawn_file_actions_adddup2(&actions, perr[1], 2); + + // child environment variables + char *path = getenv("PATH"); + if(!path) { + path = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"; + } + + cxmutstr env_path = cx_asprintf("PATH=%s", path); + + char *env[10]; + env[0] = "LC_ALL=C"; + env[1] = env_path.ptr; + env[2] = NULL; // array must be null-terminated + + // execute posix_spawn to create a child process + pid_t child; + int ret = posix_spawn( + &child, + exec, + &actions, + NULL, // attributes + args, + env); + posix_spawn_file_actions_destroy(&actions); + + close(pin[0]); + close(pout[1]); + close(perr[1]); + + if(ret) { + // posix spawn failed + perror("posix_spawn"); + // close remaining pipe fds + close(pin[1]); + close(pout[0]); + close(perr[0]); + } else { + proc.pid = child; + proc.in = pin[1]; + proc.out = pout[0]; + proc.err = perr[0]; + } + + return proc; +} + +int process_close(Process *p) { + if(p->pid == 0) { + return -1; + } + close(p->in); + close(p->out); + int status = -1; + waitpid(p->pid, &status, 0); + return status; +} + +size_t process_read(void *buf, size_t size, size_t n, Process *p) { + ssize_t r = read(p->out, buf, size*n); + return r > 0 ? r : 0; +} + +size_t process_err_read(void *buf, size_t size, size_t n, Process *p) { + ssize_t r = read(p->err, buf, size*n); + return r > 0 ? r : 0; +} + +static cxmutstr read_str(Process *p, cx_read_func readf) { + if(p->pid == 0) { + return CX_NULLSTR; + } + CxBuffer buffer; + cxBufferInit(&buffer, NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND); + cx_stream_copy(p, &buffer, readf, (cx_write_func)cxBufferWrite); + cxBufferTerminate(&buffer); + return cx_mutstrn(buffer.space, buffer.size); +} + +cxmutstr process_read_string(Process *p) { + return read_str(p, (cx_read_func)process_read); +} + +cxmutstr process_err_read_string(Process *p) { + return read_str(p, (cx_read_func)process_err_read); +} + + +// ------------------------------ wsgidav exec ----------------------------- + +static char *wsgidav_exec_path; +static Process wsgidav_process; +static int runtest_root_dir = 0; + +int wsgidav_is_available(void) { + // check if the wsgidava binary is available + char *args[] = { + "which", + "wsgidav", + NULL + }; + Process p = process_spawn("/usr/bin/which", args); + if(p.pid == 0) { + return 0; + } + + cxmutstr path = process_read_string(&p); + int status = process_close(&p); + if(status == 0) { + wsgidav_exec_path = cx_strdup(cx_strtrim(path)).ptr; + } + + free(path.ptr); + + if(status != 0) { + return 0; + } + + // check if the wsgidav config file is available + struct stat s; + if(!stat("test/wsgidav/wsgidav.yaml", &s)) { + runtest_root_dir = 1; // dav root directory + } else { + if(!stat("../test/wsgidav/wsgidav.yaml", &s)) { + runtest_root_dir = 2; // build directory + } else { + fprintf(stderr, "Error: wsgidav found, but the config file wsgidav.yaml was not found\nTry running the tests from the dav root directory\n"); + return 0; + } + } + + if(runtest_root_dir == 1) { + if(!stat("build", &s)) { + if(!S_ISDIR(s.st_mode)) { + fprintf(stderr, "Error: build is not a directory: wsgidav disabled\n"); + return 0; + } + } else { + fprintf(stderr, "Error: build directory not found: wsgidav disabled\n"); + return 0; + } + } + + return 1; +} + +int wsgidav_start(void) { + if(!wsgidav_exec_path) { + return 1; // wsgidav_start called but wsgidav_is_available return 0 + } + + if(runtest_root_dir == 1) { + if(chdir("build")) { + perror("chdir"); + return 1; + } + } + + // delete previous testrepo directory + char *args1[] = { "rm", "-Rf", "testrepo", NULL }; + Process p_rm = process_spawn("/bin/rm", args1); + cxmutstr err = process_err_read_string(&p_rm); + if(process_close(&p_rm) != 0) { + cxmutstr s = cx_strtrim(err); + fprintf(stderr, "rm failed: %.*s\n", (int)s.length, s.ptr); + return 1; + } + free(err.ptr); + + // copy testrepo to the build directory + char *args2[] = { "cp", "-R", "../test/wsgidav/testrepo", "testrepo", NULL }; + Process p_cp = process_spawn("/bin/cp", args2); + err = process_err_read_string(&p_cp); + if(process_close(&p_cp) != 0) { + cxmutstr s = cx_strtrim(err); + fprintf(stderr, "cp failed: %.*s\n", (int)s.length, s.ptr); + return 1; + } + free(err.ptr); + + // start wsgidav + char *args3[] = { "wsgidav", "-c", "../test/wsgidav/wsgidav.yaml", NULL }; + wsgidav_process = process_spawn(wsgidav_exec_path, args3); + + // TODO: parse log or try to connect to localhost + sleep(1); + + return 0; +} + +int wsgidav_stop(void) { + if(wsgidav_process.pid == 0) { + return 0; + } + kill(wsgidav_process.pid, SIGKILL); + (void)process_close(&wsgidav_process); + //if(status > 2) { + // printf("wsgidav exit: %d\n", status); + //} + return 0; +} + +/* +#else + +int wsgidav_is_available(void) { + return 0; +} + +int wsgidav_start(void) { + return 1; +} + +int wsgidav_stop(void) { + return 1; +} + +#endif + +*/ \ No newline at end of file