| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2026 Olaf Wintermann. All rights reserved. |
| |
5 * |
| |
6 * Redistribution and use in source and binary forms, with or without |
| |
7 * modification, are permitted provided that the following conditions are met: |
| |
8 * |
| |
9 * 1. Redistributions of source code must retain the above copyright |
| |
10 * notice, this list of conditions and the following disclaimer. |
| |
11 * |
| |
12 * 2. Redistributions in binary form must reproduce the above copyright |
| |
13 * notice, this list of conditions and the following disclaimer in the |
| |
14 * documentation and/or other materials provided with the distribution. |
| |
15 * |
| |
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| |
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| |
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| |
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| |
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| |
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| |
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| |
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| |
26 * POSSIBILITY OF SUCH DAMAGE. |
| |
27 */ |
| |
28 |
| |
29 #include "wsgidav.h" |
| |
30 |
| |
31 #include <stdio.h> |
| |
32 #include <stdlib.h> |
| |
33 |
| |
34 //ifndef _WIN32 |
| |
35 |
| |
36 #include <unistd.h> |
| |
37 #include <sys/fcntl.h> |
| |
38 |
| |
39 #include <spawn.h> |
| |
40 #include <sys/wait.h> |
| |
41 #include <sys/stat.h> |
| |
42 |
| |
43 #include <cx/string.h> |
| |
44 #include <cx/buffer.h> |
| |
45 #include <cx/streams.h> |
| |
46 #include <cx/printf.h> |
| |
47 |
| |
48 |
| |
49 // -------------------------- Process Utils -------------------------------- |
| |
50 |
| |
51 typedef struct { |
| |
52 pid_t pid; |
| |
53 int in; |
| |
54 int out; |
| |
55 int err; |
| |
56 } Process; |
| |
57 |
| |
58 static Process process_spawn(const char *exec, char **args) { |
| |
59 Process proc = { 0 }; |
| |
60 |
| |
61 int pin[2]; |
| |
62 int pout[2]; |
| |
63 int perr[2]; |
| |
64 |
| |
65 // child process stdin/stdout pipes |
| |
66 if(pipe(pin)) { |
| |
67 perror("pipe"); |
| |
68 return proc; |
| |
69 } |
| |
70 if(pipe(pout)) { |
| |
71 perror("pipe"); |
| |
72 close(pin[0]); |
| |
73 close(pin[1]); |
| |
74 } |
| |
75 if(pipe(perr)) { |
| |
76 perror("pipe"); |
| |
77 close(pin[0]); |
| |
78 close(pin[1]); |
| |
79 close(pout[0]); |
| |
80 close(pout[1]); |
| |
81 } |
| |
82 |
| |
83 posix_spawn_file_actions_t actions; |
| |
84 posix_spawn_file_actions_init(&actions); |
| |
85 posix_spawn_file_actions_adddup2(&actions, pin[0], 0); |
| |
86 posix_spawn_file_actions_adddup2(&actions, pout[1], 1); |
| |
87 posix_spawn_file_actions_adddup2(&actions, perr[1], 2); |
| |
88 |
| |
89 // child environment variables |
| |
90 char *path = getenv("PATH"); |
| |
91 if(!path) { |
| |
92 path = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"; |
| |
93 } |
| |
94 |
| |
95 cxmutstr env_path = cx_asprintf("PATH=%s", path); |
| |
96 |
| |
97 char *env[10]; |
| |
98 env[0] = "LC_ALL=C"; |
| |
99 env[1] = env_path.ptr; |
| |
100 env[2] = NULL; // array must be null-terminated |
| |
101 |
| |
102 // execute posix_spawn to create a child process |
| |
103 pid_t child; |
| |
104 int ret = posix_spawn( |
| |
105 &child, |
| |
106 exec, |
| |
107 &actions, |
| |
108 NULL, // attributes |
| |
109 args, |
| |
110 env); |
| |
111 posix_spawn_file_actions_destroy(&actions); |
| |
112 |
| |
113 close(pin[0]); |
| |
114 close(pout[1]); |
| |
115 close(perr[1]); |
| |
116 |
| |
117 if(ret) { |
| |
118 // posix spawn failed |
| |
119 perror("posix_spawn"); |
| |
120 // close remaining pipe fds |
| |
121 close(pin[1]); |
| |
122 close(pout[0]); |
| |
123 close(perr[0]); |
| |
124 } else { |
| |
125 proc.pid = child; |
| |
126 proc.in = pin[1]; |
| |
127 proc.out = pout[0]; |
| |
128 proc.err = perr[0]; |
| |
129 } |
| |
130 |
| |
131 return proc; |
| |
132 } |
| |
133 |
| |
134 int process_close(Process *p) { |
| |
135 if(p->pid == 0) { |
| |
136 return -1; |
| |
137 } |
| |
138 close(p->in); |
| |
139 close(p->out); |
| |
140 int status = -1; |
| |
141 waitpid(p->pid, &status, 0); |
| |
142 return status; |
| |
143 } |
| |
144 |
| |
145 size_t process_read(void *buf, size_t size, size_t n, Process *p) { |
| |
146 ssize_t r = read(p->out, buf, size*n); |
| |
147 return r > 0 ? r : 0; |
| |
148 } |
| |
149 |
| |
150 size_t process_err_read(void *buf, size_t size, size_t n, Process *p) { |
| |
151 ssize_t r = read(p->err, buf, size*n); |
| |
152 return r > 0 ? r : 0; |
| |
153 } |
| |
154 |
| |
155 static cxmutstr read_str(Process *p, cx_read_func readf) { |
| |
156 if(p->pid == 0) { |
| |
157 return CX_NULLSTR; |
| |
158 } |
| |
159 CxBuffer buffer; |
| |
160 cxBufferInit(&buffer, NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND); |
| |
161 cx_stream_copy(p, &buffer, readf, (cx_write_func)cxBufferWrite); |
| |
162 cxBufferTerminate(&buffer); |
| |
163 return cx_mutstrn(buffer.space, buffer.size); |
| |
164 } |
| |
165 |
| |
166 cxmutstr process_read_string(Process *p) { |
| |
167 return read_str(p, (cx_read_func)process_read); |
| |
168 } |
| |
169 |
| |
170 cxmutstr process_err_read_string(Process *p) { |
| |
171 return read_str(p, (cx_read_func)process_err_read); |
| |
172 } |
| |
173 |
| |
174 |
| |
175 // ------------------------------ wsgidav exec ----------------------------- |
| |
176 |
| |
177 static char *wsgidav_exec_path; |
| |
178 static Process wsgidav_process; |
| |
179 static int runtest_root_dir = 0; |
| |
180 |
| |
181 int wsgidav_is_available(void) { |
| |
182 // check if the wsgidava binary is available |
| |
183 char *args[] = { |
| |
184 "which", |
| |
185 "wsgidav", |
| |
186 NULL |
| |
187 }; |
| |
188 Process p = process_spawn("/usr/bin/which", args); |
| |
189 if(p.pid == 0) { |
| |
190 return 0; |
| |
191 } |
| |
192 |
| |
193 cxmutstr path = process_read_string(&p); |
| |
194 int status = process_close(&p); |
| |
195 if(status == 0) { |
| |
196 wsgidav_exec_path = cx_strdup(cx_strtrim(path)).ptr; |
| |
197 } |
| |
198 |
| |
199 free(path.ptr); |
| |
200 |
| |
201 if(status != 0) { |
| |
202 return 0; |
| |
203 } |
| |
204 |
| |
205 // check if the wsgidav config file is available |
| |
206 struct stat s; |
| |
207 if(!stat("test/wsgidav/wsgidav.yaml", &s)) { |
| |
208 runtest_root_dir = 1; // dav root directory |
| |
209 } else { |
| |
210 if(!stat("../test/wsgidav/wsgidav.yaml", &s)) { |
| |
211 runtest_root_dir = 2; // build directory |
| |
212 } else { |
| |
213 fprintf(stderr, "Error: wsgidav found, but the config file wsgidav.yaml was not found\nTry running the tests from the dav root directory\n"); |
| |
214 return 0; |
| |
215 } |
| |
216 } |
| |
217 |
| |
218 if(runtest_root_dir == 1) { |
| |
219 if(!stat("build", &s)) { |
| |
220 if(!S_ISDIR(s.st_mode)) { |
| |
221 fprintf(stderr, "Error: build is not a directory: wsgidav disabled\n"); |
| |
222 return 0; |
| |
223 } |
| |
224 } else { |
| |
225 fprintf(stderr, "Error: build directory not found: wsgidav disabled\n"); |
| |
226 return 0; |
| |
227 } |
| |
228 } |
| |
229 |
| |
230 return 1; |
| |
231 } |
| |
232 |
| |
233 int wsgidav_start(void) { |
| |
234 if(!wsgidav_exec_path) { |
| |
235 return 1; // wsgidav_start called but wsgidav_is_available return 0 |
| |
236 } |
| |
237 |
| |
238 if(runtest_root_dir == 1) { |
| |
239 if(chdir("build")) { |
| |
240 perror("chdir"); |
| |
241 return 1; |
| |
242 } |
| |
243 } |
| |
244 |
| |
245 // delete previous testrepo directory |
| |
246 char *args1[] = { "rm", "-Rf", "testrepo", NULL }; |
| |
247 Process p_rm = process_spawn("/bin/rm", args1); |
| |
248 cxmutstr err = process_err_read_string(&p_rm); |
| |
249 if(process_close(&p_rm) != 0) { |
| |
250 cxmutstr s = cx_strtrim(err); |
| |
251 fprintf(stderr, "rm failed: %.*s\n", (int)s.length, s.ptr); |
| |
252 return 1; |
| |
253 } |
| |
254 free(err.ptr); |
| |
255 |
| |
256 // copy testrepo to the build directory |
| |
257 char *args2[] = { "cp", "-R", "../test/wsgidav/testrepo", "testrepo", NULL }; |
| |
258 Process p_cp = process_spawn("/bin/cp", args2); |
| |
259 err = process_err_read_string(&p_cp); |
| |
260 if(process_close(&p_cp) != 0) { |
| |
261 cxmutstr s = cx_strtrim(err); |
| |
262 fprintf(stderr, "cp failed: %.*s\n", (int)s.length, s.ptr); |
| |
263 return 1; |
| |
264 } |
| |
265 free(err.ptr); |
| |
266 |
| |
267 // start wsgidav |
| |
268 char *args3[] = { "wsgidav", "-c", "../test/wsgidav/wsgidav.yaml", NULL }; |
| |
269 wsgidav_process = process_spawn(wsgidav_exec_path, args3); |
| |
270 |
| |
271 // TODO: parse log or try to connect to localhost |
| |
272 sleep(1); |
| |
273 |
| |
274 return 0; |
| |
275 } |
| |
276 |
| |
277 int wsgidav_stop(void) { |
| |
278 if(wsgidav_process.pid == 0) { |
| |
279 return 0; |
| |
280 } |
| |
281 kill(wsgidav_process.pid, SIGKILL); |
| |
282 (void)process_close(&wsgidav_process); |
| |
283 //if(status > 2) { |
| |
284 // printf("wsgidav exit: %d\n", status); |
| |
285 //} |
| |
286 return 0; |
| |
287 } |
| |
288 |
| |
289 /* |
| |
290 #else |
| |
291 |
| |
292 int wsgidav_is_available(void) { |
| |
293 return 0; |
| |
294 } |
| |
295 |
| |
296 int wsgidav_start(void) { |
| |
297 return 1; |
| |
298 } |
| |
299 |
| |
300 int wsgidav_stop(void) { |
| |
301 return 1; |
| |
302 } |
| |
303 |
| |
304 #endif |
| |
305 |
| |
306 */ |