From 3a1f887928405cbec690424177700b8346e1eb74 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 11 Oct 2023 20:26:28 -0700 Subject: [PATCH] Introduce posix_spawn_file_actions_addchdir_np() --- examples/examples.mk | 2 + examples/greenbean.c | 319 ++++++----- examples/greenbean2.c | 515 ------------------ libc/calls/mkntpath.c | 57 +- libc/calls/mkntpathat.c | 39 +- libc/calls/ntspawn.c | 7 +- libc/calls/pledge.c | 7 +- libc/calls/syscall_support-nt.internal.h | 2 + libc/intrin/createprocess.c | 2 +- libc/intrin/ulock.c | 8 +- libc/isystem/cosmo.h | 2 + libc/proc/execve-nt.greg.c | 7 +- libc/proc/fork-nt.c | 3 +- libc/proc/ntspawn.h | 4 +- libc/proc/posix_spawn.c | 90 ++- libc/proc/posix_spawn.h | 3 + libc/proc/posix_spawn.internal.h | 8 +- .../posix_spawn_file_actions_addchdir_np.c | 41 ++ .../posix_spawn_file_actions_addfchdir_np.c | 39 ++ libc/proc/posix_spawn_file_actions_addopen.c | 4 +- libc/thread/pthread_setname_np.c | 2 +- net/http/parsehttpmessage.c | 2 +- test/libc/calls/pledge_test.c | 6 + test/libc/proc/posix_spawn_test.c | 24 + test/libc/proc/test.mk | 1 + 25 files changed, 446 insertions(+), 748 deletions(-) delete mode 100644 examples/greenbean2.c create mode 100644 libc/proc/posix_spawn_file_actions_addchdir_np.c create mode 100644 libc/proc/posix_spawn_file_actions_addfchdir_np.c diff --git a/examples/examples.mk b/examples/examples.mk index 9c30c18a3..84e477f42 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -97,6 +97,8 @@ EXAMPLES_DIRECTDEPS = \ EXAMPLES_DEPS := \ $(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)))) +$(EXAMPLES_OBJS): override CFLAGS += -isystem libc/isystem + o/$(MODE)/examples/examples.pkg: \ $(EXAMPLES_OBJS) \ $(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)_A).pkg) diff --git a/examples/greenbean.c b/examples/greenbean.c index 40c51476d..9ab1dc093 100644 --- a/examples/greenbean.c +++ b/examples/greenbean.c @@ -7,6 +7,22 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif +#ifdef __COSMOCC__ +#define _COSMO_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else #include "libc/assert.h" #include "libc/atomic.h" #include "libc/calls/calls.h" @@ -20,65 +36,42 @@ #include "libc/fmt/itoa.h" #include "libc/intrin/kprintf.h" #include "libc/log/log.h" -#include "libc/mem/gc.internal.h" +#include "libc/macros.internal.h" +#include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/sock/struct/sockaddr.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/so.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/tcp.h" +#include "libc/sysv/consts/timer.h" #include "libc/thread/thread.h" #include "libc/thread/thread2.h" #include "net/http/http.h" +#endif /** - * @fileoverview greenbean lightweight threaded web server - * - * $ make -j8 o//tool/net/greenbean.com - * $ o//tool/net/greenbean.com & - * $ printf 'GET /\n\n' | nc 127.0.0.1 8080 - * HTTP/1.1 200 OK - * Server: greenbean/1.o - * Referrer-Policy: origin - * Cache-Control: private; max-age=0 - * Content-Type: text/html; charset=utf-8 - * Date: Sat, 14 May 2022 14:13:07 GMT - * Content-Length: 118 - * - * - * hello world - *

hello world

- *

this is a fun webpage - *

hosted by greenbean - * - * Like redbean, greenbean has superior performance too, with an - * advantage on benchmarks biased towards high connection counts - * - * $ wrk -c 300 -t 32 --latency http://127.0.0.1:8080/ - * Running 10s test @ http://127.0.0.1:8080/ - * 32 threads and 300 connections - * Thread Stats Avg Stdev Max +/- Stdev - * Latency 661.06us 5.11ms 96.22ms 98.85% - * Req/Sec 42.38k 8.90k 90.47k 84.65% - * Latency Distribution - * 50% 184.00us - * 75% 201.00us - * 90% 224.00us - * 99% 11.99ms - * 10221978 requests in 7.60s, 3.02GB read - * Requests/sec: 1345015.69 - * Transfer/sec: 406.62MB + * @fileoverview greenbean lightweight threaded web server no. 2 * + * This web server is the same as greenbean.c except it supports having + * more than one thread on Windows. To do that we have to make the code + * more complicated by not using SO_REUSEPORT. The approach we take, is + * creating a single listener thread which adds accepted sockets into a + * queue that worker threads consume. This way, if you like Windows you + * can easily have a web server with 10,000+ connections. */ #define PORT 8080 -#define KEEPALIVE 30000 +#define KEEPALIVE 5000 #define LOGGING 1 #define STANDARD_RESPONSE_HEADERS \ @@ -86,21 +79,30 @@ "Referrer-Policy: origin\r\n" \ "Cache-Control: private; max-age=0\r\n" -int threads; -int alwaysclose; +int server; atomic_int a_termsig; atomic_int a_workers; atomic_int a_messages; -atomic_int a_listening; atomic_int a_connections; pthread_cond_t statuscond; pthread_mutex_t statuslock; const char *volatile status = ""; +#if LOGGING +// prints persistent status line +// \r moves cursor back to beginning of line +// \e[K clears text from cursor to end of line +#define LOG(FMT, ...) kprintf("\r\e[K" FMT "\n", ##__VA_ARGS__) +#else +#define LOG(FMT, ...) (void)0 +#endif + +// updates the status line if it's convenient to do so void SomethingHappened(void) { unassert(!pthread_cond_signal(&statuscond)); } +// performs a guaranteed update of the main thread status line void SomethingImportantHappened(void) { unassert(!pthread_mutex_lock(&statuslock)); unassert(!pthread_cond_signal(&statuscond)); @@ -108,105 +110,96 @@ void SomethingImportantHappened(void) { } void *Worker(void *id) { - int server, yes = 1; - - // load balance incoming connections for port 8080 across all threads - // hangup on any browser clients that lag for more than a few seconds - struct timeval timeo = {KEEPALIVE / 1000, KEEPALIVE % 1000}; - struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)}; - - server = socket(AF_INET, SOCK_STREAM, 0); - if (server == -1) { - kprintf("\r\e[Ksocket() failed %m\n"); - if (errno == ENFILE || errno == EMFILE) { - TooManyFileDescriptors: - kprintf("sudo prlimit --pid=$$ --nofile=%d\n", threads * 3); - } - goto WorkerFinished; - } - - // we don't bother checking for errors here since OS support for the - // advanced features tends to be a bit spotty and harmless to ignore - setsockopt(server, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); - setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); - setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - setsockopt(server, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); - setsockopt(server, SOL_TCP, TCP_FASTOPEN, &yes, sizeof(yes)); - setsockopt(server, SOL_TCP, TCP_QUICKACK, &yes, sizeof(yes)); - errno = 0; - - // open our ears to incoming connections; so_reuseport makes it - // possible for our many threads to bind to the same interface! - // otherwise we'd need to create a complex multi-threaded queue - if (bind(server, (struct sockaddr *)&addr, sizeof(addr)) == -1) { - kprintf("\r\e[Ksocket() returned %m\n"); - goto CloseWorker; - } - unassert(!listen(server, 1)); + pthread_setname_np(pthread_self(), "Worker"); // connection loop - ++a_listening; - SomethingImportantHappened(); while (!a_termsig) { - uint32_t clientaddrsize; + int client; + uint32_t clientsize; + int inmsglen, outmsglen; struct sockaddr_in clientaddr; - int client, inmsglen, outmsglen; - char inbuf[512], outbuf[512], *p, *q; + char inbuf[1500], outbuf[1500], *p, *q; // musl libc and cosmopolitan libc support a posix thread extension // that makes thread cancelation work much better. your io routines - // will just raise ECANCELED so you can check for cancellation with + // will just raise ECANCELED, so you can check for cancelation with // normal logic rather than needing to push and pop cleanup handler // functions onto the stack, or worse dealing with async interrupts unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); // wait for client connection - // we don't bother with poll() because this is actually very speedy - clientaddrsize = sizeof(clientaddr); - client = accept(server, (struct sockaddr *)&clientaddr, &clientaddrsize); + clientsize = sizeof(clientaddr); + client = accept(server, (struct sockaddr *)&clientaddr, &clientsize); - // turns cancellation off so we don't interrupt active http clients + // turn cancel off, so we don't need to check write() for ecanceled unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); if (client == -1) { - if (errno != EAGAIN && errno != ECANCELED) { - kprintf("\r\e[Kaccept() returned %m\n"); - if (errno == ENFILE || errno == EMFILE) { - goto TooManyFileDescriptors; - } - usleep(10000); - } + // accept() errors are generally ephemeral or recoverable + // it'd potentially be a good idea to exponential backoff here + if (errno == ECANCELED) continue; // pthread_cancel() was called + LOG("accept() returned %m"); + SomethingHappened(); continue; } + // this causes read() and write() to raise eagain after some time + struct timeval timeo = {KEEPALIVE / 1000, KEEPALIVE % 1000}; + setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); + + // log the incoming http message + unsigned clientip = ntohl(clientaddr.sin_addr.s_addr); ++a_connections; + LOG("%6H accepted connection from %hhu.%hhu.%hhu.%hhu:%hu", clientip >> 24, + clientip >> 16, clientip >> 8, clientip, ntohs(clientaddr.sin_port)); SomethingHappened(); + (void)clientip; // message loop ssize_t got, sent; struct HttpMessage msg; do { - // parse the incoming http message - InitHttpMessage(&msg, kHttpRequest); - // wait for http message (non-fragmented required) - // we're not terrible concerned when errors happen here - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); - if ((got = read(client, inbuf, sizeof(inbuf))) <= 0) break; - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); - // check that client message wasn't fragmented into more reads - if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) break; - ++a_messages; - SomethingHappened(); -#if LOGGING - // log the incoming http message - unsigned clientip = ntohl(clientaddr.sin_addr.s_addr); - kprintf("\r\e[K%6P get some %hhu.%hhu.%hhu.%hhu:%hu %#.*s\n", - clientip >> 24, clientip >> 16, clientip >> 8, clientip, - ntohs(clientaddr.sin_port), msg.uri.b - msg.uri.a, - inbuf + msg.uri.a); + // wait for next http message (non-fragmented required) + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); + got = read(client, inbuf, sizeof(inbuf)); + for (int i = 0; i < got; ++i) { + if (!inbuf[i]) inbuf[i] = 1; + } + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + if (got <= 0) { + if (!got) { + LOG("%6H client disconnected"); + } else if (errno == EAGAIN) { + LOG("%6H client timed out"); + } else if (errno == ECANCELED) { + LOG("%6H disconnecting client due to shutdown"); + } else { + LOG("%6H read() returned %m"); + } + SomethingHappened(); + break; + } + + // check that client message wasn't fragmented into more reads + InitHttpMessage(&msg, kHttpRequest); + if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) { + if (!inmsglen) { + LOG("%6H client sent fragmented message"); + } else { + LOG("%6H client sent bad message"); + } + SomethingHappened(); + break; + } + + // update server status with details of new message + ++a_messages; + LOG("%6H received message from %hhu.%hhu.%hhu.%hhu:%hu for path %#.*s", + clientip >> 24, clientip >> 16, clientip >> 8, clientip, + ntohs(clientaddr.sin_port), msg.uri.b - msg.uri.a, inbuf + msg.uri.a); SomethingHappened(); -#endif // display hello world html page for http://127.0.0.1:8080/ struct tm tm; @@ -226,9 +219,6 @@ void *Worker(void *id) { p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); p = stpcpy(p, "\r\nContent-Length: "); p = FormatInt32(p, strlen(q)); - if (alwaysclose) { - p = stpcpy(p, "\r\nConnection: close"); - } p = stpcpy(p, "\r\n\r\n"); p = stpcpy(p, q); outmsglen = p - outbuf; @@ -247,9 +237,6 @@ void *Worker(void *id) { p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); p = stpcpy(p, "\r\nContent-Length: "); p = FormatInt32(p, strlen(q)); - if (alwaysclose) { - p = stpcpy(p, "\r\nConnection: close"); - } p = stpcpy(p, "\r\n\r\n"); p = stpcpy(p, q); outmsglen = p - outbuf; @@ -260,36 +247,29 @@ void *Worker(void *id) { // amount, then since we sent the content length and checked // that the client didn't attach a payload, we are so synced // thus we can safely process more messages - } while (!alwaysclose && // - got == inmsglen && // + } while (got == inmsglen && // sent == outmsglen && // !msg.headers[kHttpContentLength].a && !msg.headers[kHttpTransferEncoding].a && (msg.method == kHttpGet || msg.method == kHttpHead)); + DestroyHttpMessage(&msg); - close(client); --a_connections; SomethingHappened(); + close(client); } - --a_listening; - // inform the parent that this clone has finished -CloseWorker: - close(server); -WorkerFinished: --a_workers; - SomethingImportantHappened(); return 0; } -void PrintStatus(void) { +void PrintEphemeralStatusLine(void) { kprintf("\r\e[K\e[32mgreenbean\e[0m " "workers=%d " - "listening=%d " "connections=%d " "messages=%d%s ", - a_workers, a_listening, a_connections, a_messages, status); + a_workers, a_connections, a_messages, status); } void OnTerm(int sig) { @@ -312,29 +292,38 @@ int main(int argc, char *argv[]) { unassert(!sigaction(SIGHUP, &sa, 0)); unassert(!sigaction(SIGTERM, &sa, 0)); - // print all the ips that 0.0.0.0 would bind - // Cosmo's GetHostIps() API is much easier than ioctl(SIOCGIFCONF) - uint32_t *hostips; - for (hostips = gc(GetHostIps()), i = 0; hostips[i]; ++i) { - kprintf("listening on http://%hhu.%hhu.%hhu.%hhu:%hu\n", hostips[i] >> 24, - hostips[i] >> 16, hostips[i] >> 8, hostips[i], PORT); - } - // you can pass the number of threads you want as the first command arg - threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count(); + int threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count(); if (!(1 <= threads && threads <= 100000)) { - kprintf("\r\e[Kerror: invalid number of threads: %d\n", threads); + tinyprint(2, "error: invalid number of threads\n", NULL); exit(1); } - // caveat emptor microsofties - if (IsWindows()) { - kprintf("sorry but windows isn't supported by the greenbean demo yet\n" - "because it doesn't support SO_REUSEPORT which is a nice for\n" - "gaining great performance on UNIX systems, with simple code\n" - "however windows will work fine if we limit it to one thread\n"); - threads = 1; // we're going to make just one web server thread - alwaysclose = 1; // don't let client idle, since it'd block others + // create listening socket that'll be shared by threads + int yes = 1; + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)}; + server = socket(AF_INET, SOCK_STREAM, 0); + if (server == -1) { + perror("socket"); + exit(1); + } + setsockopt(server, SOL_TCP, TCP_FASTOPEN, &yes, sizeof(yes)); + setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + if (bind(server, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + perror("bind"); + exit(1); + } + if (listen(server, SOMAXCONN)) { + perror("listen"); + exit(1); + } + + // print all the ips that 0.0.0.0 would bind + // Cosmo's GetHostIps() API is much easier than ioctl(SIOCGIFCONF) + uint32_t *hostips; + for (hostips = _gc(GetHostIps()), i = 0; hostips[i]; ++i) { + kprintf("listening on http://%hhu.%hhu.%hhu.%hhu:%hu\n", hostips[i] >> 24, + hostips[i] >> 16, hostips[i] >> 8, hostips[i], PORT); } // secure the server @@ -385,14 +374,16 @@ int main(int argc, char *argv[]) { unassert(!pthread_attr_setstacksize(&attr, 65536)); unassert(!pthread_attr_setguardsize(&attr, pagesz)); unassert(!pthread_attr_setsigmask_np(&attr, &block)); - pthread_t *th = gc(calloc(threads, sizeof(pthread_t))); + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + pthread_t *th = _gc(calloc(threads, sizeof(pthread_t))); for (i = 0; i < threads; ++i) { int rc; ++a_workers; if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) { --a_workers; - kprintf("\r\e[Kpthread_create failed: %s\n", strerror(rc)); + kprintf("pthread_create failed: %s\n", strerror(rc)); if (rc == EAGAIN) { + kprintf("sudo prlimit --pid=$$ --nofile=%d\n", threads * 2); kprintf("sudo prlimit --pid=$$ --nproc=%d\n", threads * 2); } if (!i) exit(1); @@ -400,17 +391,21 @@ int main(int argc, char *argv[]) { break; } if (!(i % 50)) { - PrintStatus(); + PrintEphemeralStatusLine(); } } unassert(!pthread_attr_destroy(&attr)); - // wait for workers to terminate + // show status line on terminal until terminated + struct timespec tick = timespec_real(); unassert(!pthread_mutex_lock(&statuslock)); while (!a_termsig) { - PrintStatus(); + PrintEphemeralStatusLine(); unassert(!pthread_cond_wait(&statuscond, &statuslock)); - usleep(10 * 1000); + // limit status line updates to sixty frames per second + do tick = timespec_add(tick, (struct timespec){0, 1e9 / 60}); + while (timespec_cmp(tick, timespec_real()) < 0); + clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &tick, 0); } unassert(!pthread_mutex_unlock(&statuslock)); @@ -421,11 +416,14 @@ int main(int argc, char *argv[]) { pthread_cancel(th[i]); } + // on windows this is the only way accept() can be canceled + if (IsWindows()) close(server); + // print status in terminal as the shutdown progresses unassert(!pthread_mutex_lock(&statuslock)); while (a_workers) { unassert(!pthread_cond_wait(&statuscond, &statuslock)); - PrintStatus(); + PrintEphemeralStatusLine(); } unassert(!pthread_mutex_unlock(&statuslock)); @@ -434,19 +432,18 @@ int main(int argc, char *argv[]) { unassert(!pthread_join(th[i], 0)); } + // close the server socket + if (!IsWindows()) close(server); + // clean up terminal line - kprintf("\r\e[Kthank you for choosing \e[32mgreenbean\e[0m\n"); + LOG("thank you for choosing \e[32mgreenbean\e[0m"); // clean up more resources - unassert(!pthread_mutex_destroy(&statuslock)); unassert(!pthread_cond_destroy(&statuscond)); + unassert(!pthread_mutex_destroy(&statuslock)); // quality assurance if (IsModeDbg()) { CheckForMemoryLeaks(); } - - // propagate termination signal - signal(a_termsig, SIG_DFL); - raise(a_termsig); } diff --git a/examples/greenbean2.c b/examples/greenbean2.c deleted file mode 100644 index 2b029b2e9..000000000 --- a/examples/greenbean2.c +++ /dev/null @@ -1,515 +0,0 @@ -#if 0 -/*─────────────────────────────────────────────────────────────────╗ -│ To the extent possible under law, Justine Tunney has waived │ -│ all copyright and related or neighboring rights to this file, │ -│ as it is written in the following disclaimers: │ -│ • http://unlicense.org/ │ -│ • http://creativecommons.org/publicdomain/zero/1.0/ │ -╚─────────────────────────────────────────────────────────────────*/ -#endif -#include "libc/assert.h" -#include "libc/atomic.h" -#include "libc/calls/calls.h" -#include "libc/calls/pledge.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/timespec.h" -#include "libc/calls/struct/timeval.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/fmt/conv.h" -#include "libc/fmt/itoa.h" -#include "libc/intrin/kprintf.h" -#include "libc/log/log.h" -#include "libc/macros.internal.h" -#include "libc/mem/gc.internal.h" -#include "libc/mem/mem.h" -#include "libc/runtime/runtime.h" -#include "libc/sock/sock.h" -#include "libc/sock/struct/sockaddr.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/af.h" -#include "libc/sysv/consts/auxv.h" -#include "libc/sysv/consts/sig.h" -#include "libc/sysv/consts/so.h" -#include "libc/sysv/consts/sock.h" -#include "libc/sysv/consts/sol.h" -#include "libc/sysv/consts/tcp.h" -#include "libc/thread/thread.h" -#include "libc/thread/thread2.h" -#include "net/http/http.h" -#include "third_party/nsync/cv.h" -#include "third_party/nsync/mu.h" -#include "third_party/nsync/time.h" - -/** - * @fileoverview greenbean lightweight threaded web server no. 2 - * - * This web server is the same as greenbean.c except it supports having - * more than one thread on Windows. To do that we have to make the code - * more complicated by not using SO_REUSEPORT. The approach we take, is - * creating a single listener thread which adds accepted sockets into a - * queue that worker threads consume. This way, if you like Windows you - * can easily have a web server with 10,000+ connections. - */ - -#define PORT 8080 -#define KEEPALIVE 30000 -#define LOGGING 1 - -#define STANDARD_RESPONSE_HEADERS \ - "Server: greenbean/1.o\r\n" \ - "Referrer-Policy: origin\r\n" \ - "Cache-Control: private; max-age=0\r\n" - -int server; -int threads; -pthread_t listener; -atomic_int a_termsig; -atomic_int a_workers; -atomic_int a_messages; -atomic_int a_connections; -pthread_cond_t statuscond; -pthread_mutex_t statuslock; -const char *volatile status = ""; - -struct Clients { - int pos; - int count; - pthread_mutex_t mu; - pthread_cond_t non_full; - pthread_cond_t non_empty; - struct Client { - int sock; - uint32_t size; - struct sockaddr_in addr; - } data[100]; -} g_clients; - -ssize_t Write(int fd, const char *s) { - return write(fd, s, strlen(s)); -} - -void SomethingHappened(void) { - unassert(!pthread_cond_signal(&statuscond)); -} - -void SomethingImportantHappened(void) { - unassert(!pthread_mutex_lock(&statuslock)); - unassert(!pthread_cond_signal(&statuscond)); - unassert(!pthread_mutex_unlock(&statuslock)); -} - -bool AddClient(struct Clients *q, const struct Client *v, - struct timespec *deadline) { - bool wake = false; - bool added = false; - pthread_mutex_lock(&q->mu); - while (q->count == ARRAYLEN(q->data)) { - if (pthread_cond_timedwait(&q->non_full, &q->mu, deadline)) { - break; // must be ETIMEDOUT or ECANCELED - } - } - if (q->count != ARRAYLEN(q->data)) { - int i = q->pos + q->count; - if (ARRAYLEN(q->data) <= i) i -= ARRAYLEN(q->data); - memcpy(q->data + i, v, sizeof(*v)); - if (!q->count) wake = true; - q->count++; - added = true; - } - pthread_mutex_unlock(&q->mu); - if (wake) pthread_cond_signal(&q->non_empty); - return added; -} - -int GetClient(struct Clients *q, struct Client *out) { - int got = 0, len = 1; - pthread_mutex_lock(&q->mu); - while (!q->count) { - errno_t err; - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); - err = pthread_cond_wait(&q->non_empty, &q->mu); - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); - if (err) { - unassert(err == ECANCELED); - break; - } - } - while (got < len && q->count) { - memcpy(out + got, q->data + q->pos, sizeof(*out)); - if (q->count == ARRAYLEN(q->data)) { - pthread_cond_broadcast(&q->non_full); - } - ++got; - q->pos++; - q->count--; - if (q->pos == ARRAYLEN(q->data)) q->pos = 0; - } - pthread_mutex_unlock(&q->mu); - return got; -} - -void *ListenWorker(void *arg) { - int yes = 1; - pthread_setname_np(pthread_self(), "Listener"); - - // load balance incoming connections for port 8080 across all threads - // hangup on any browser clients that lag for more than a few seconds - struct timeval timeo = {KEEPALIVE / 1000, KEEPALIVE % 1000}; - struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)}; - - server = socket(AF_INET, SOCK_STREAM, 0); - if (server == -1) { - kprintf("\r\e[Ksocket() failed %m\n"); - SomethingHappened(); - return 0; - } - - // we don't bother checking for errors here since OS support for the - // advanced features tends to be a bit spotty and harmless to ignore - setsockopt(server, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); - setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); - setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - setsockopt(server, SOL_TCP, TCP_FASTOPEN, &yes, sizeof(yes)); - setsockopt(server, SOL_TCP, TCP_QUICKACK, &yes, sizeof(yes)); - errno = 0; - - // open our ears to incoming connections; so_reuseport makes it - // possible for our many threads to bind to the same interface! - // otherwise we'd need to create a complex multi-threaded queue - if (bind(server, (struct sockaddr *)&addr, sizeof(addr)) == -1) { - kprintf("\r\e[Kbind() returned %m\n"); - SomethingHappened(); - goto CloseServer; - } - unassert(!listen(server, 1)); - - while (!a_termsig) { - struct Client client; - - // musl libc and cosmopolitan libc support a posix thread extension - // that makes thread cancelation work much better. your io routines - // will just raise ECANCELED, so you can check for cancelation with - // normal logic rather than needing to push and pop cleanup handler - // functions onto the stack, or worse dealing with async interrupts - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); - - // wait for client connection - client.size = sizeof(client.addr); - client.sock = accept(server, (struct sockaddr *)&client.addr, &client.size); - - // turn cancel off, so we don't need to check write() for ecanceled - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); - - if (client.sock == -1) { - // accept() errors are generally ephemeral or recoverable - if (errno == EAGAIN) continue; // SO_RCVTIMEO interval churns - if (errno == ECANCELED) continue; // pthread_cancel() was called - kprintf("\r\e[Kaccept() returned %m\n"); - SomethingHappened(); - usleep(10000); - errno = 0; - continue; - } - -#if LOGGING - // log the incoming http message - unsigned clientip = ntohl(client.addr.sin_addr.s_addr); - kprintf("\r\e[K%6P accepted connection from %hhu.%hhu.%hhu.%hhu:%hu\n", - clientip >> 24, clientip >> 16, clientip >> 8, clientip, - ntohs(client.addr.sin_port)); - SomethingHappened(); -#endif - - ++a_connections; - SomethingHappened(); - struct timespec deadline = - timespec_add(timespec_real(), timespec_frommillis(100)); - if (!AddClient(&g_clients, &client, &deadline)) { - Write(client.sock, "HTTP/1.1 503 Accept Queue Full\r\n" - "Content-Type: text/plain\r\n" - "Connection: close\r\n" - "\r\n" - "Accept Queue Full\n"); - close(client.sock); - } - } - -CloseServer: - SomethingHappened(); - close(server); - return 0; -} - -void *Worker(void *id) { - pthread_setname_np(pthread_self(), "Worker"); - - // connection loop - while (!a_termsig) { - struct Client client; - int inmsglen, outmsglen; - char inbuf[512], outbuf[512], *p, *q; - - // find a client to serve - if (!GetClient(&g_clients, &client)) { - continue; // should be due to ecanceled - } - - // message loop - ssize_t got, sent; - struct HttpMessage msg; - do { - // parse the incoming http message - InitHttpMessage(&msg, kHttpRequest); - // wait for http message (non-fragmented required) - // we're not terribly concerned when errors happen here - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); - if ((got = read(client.sock, inbuf, sizeof(inbuf))) <= 0) break; - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); - // check that client message wasn't fragmented into more reads - if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) break; - ++a_messages; - SomethingHappened(); - -#if LOGGING - // log the incoming http message - unsigned clientip = ntohl(client.addr.sin_addr.s_addr); - kprintf("\r\e[K%6P get some %hhu.%hhu.%hhu.%hhu:%hu %#.*s\n", - clientip >> 24, clientip >> 16, clientip >> 8, clientip, - ntohs(client.addr.sin_port), msg.uri.b - msg.uri.a, - inbuf + msg.uri.a); - SomethingHappened(); -#endif - - // display hello world html page for http://127.0.0.1:8080/ - struct tm tm; - int64_t unixts; - struct timespec ts; - if (msg.method == kHttpGet && - (msg.uri.b - msg.uri.a == 1 && inbuf[msg.uri.a + 0] == '/')) { - q = "\r\n" - "hello world\r\n" - "

hello world

\r\n" - "

this is a fun webpage\r\n" - "

hosted by greenbean\r\n"; - p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS - "Content-Type: text/html; charset=utf-8\r\n" - "Date: "); - clock_gettime(0, &ts), unixts = ts.tv_sec; - p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); - p = stpcpy(p, "\r\nContent-Length: "); - p = FormatInt32(p, strlen(q)); - p = stpcpy(p, "\r\n\r\n"); - p = stpcpy(p, q); - outmsglen = p - outbuf; - sent = write(client.sock, outbuf, outmsglen); - - } else { - // display 404 not found error page for every thing else - q = "\r\n" - "404 not found\r\n" - "

404 not found

\r\n"; - p = stpcpy(outbuf, - "HTTP/1.1 404 Not Found\r\n" STANDARD_RESPONSE_HEADERS - "Content-Type: text/html; charset=utf-8\r\n" - "Date: "); - clock_gettime(0, &ts), unixts = ts.tv_sec; - p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); - p = stpcpy(p, "\r\nContent-Length: "); - p = FormatInt32(p, strlen(q)); - p = stpcpy(p, "\r\n\r\n"); - p = stpcpy(p, q); - outmsglen = p - outbuf; - sent = write(client.sock, outbuf, p - outbuf); - } - - // if the client isn't pipelining and write() wrote the full - // amount, then since we sent the content length and checked - // that the client didn't attach a payload, we are so synced - // thus we can safely process more messages - } while (got == inmsglen && // - sent == outmsglen && // - !msg.headers[kHttpContentLength].a && - !msg.headers[kHttpTransferEncoding].a && - (msg.method == kHttpGet || msg.method == kHttpHead)); - DestroyHttpMessage(&msg); - kprintf("\r\e[K%6P client disconnected\n"); - SomethingHappened(); - close(client.sock); - --a_connections; - SomethingHappened(); - } - - --a_workers; - SomethingImportantHappened(); - return 0; -} - -void PrintStatus(void) { - kprintf("\r\e[K\e[32mgreenbean\e[0m " - "workers=%d " - "connections=%d " - "messages=%d%s ", - a_workers, a_connections, a_messages, status); -} - -void OnTerm(int sig) { - a_termsig = sig; - status = " shutting down..."; - SomethingHappened(); -} - -int main(int argc, char *argv[]) { - int i; - - // print cpu registers and backtrace on crash - // note that pledge'll makes backtraces worse - // you can press ctrl+\ to trigger backtraces - // ShowCrashReports(); - - // listen for ctrl-c, terminal close, and kill - struct sigaction sa = {.sa_handler = OnTerm}; - unassert(!sigaction(SIGINT, &sa, 0)); - unassert(!sigaction(SIGHUP, &sa, 0)); - unassert(!sigaction(SIGTERM, &sa, 0)); - - // print all the ips that 0.0.0.0 would bind - // Cosmo's GetHostIps() API is much easier than ioctl(SIOCGIFCONF) - uint32_t *hostips; - for (hostips = gc(GetHostIps()), i = 0; hostips[i]; ++i) { - kprintf("listening on http://%hhu.%hhu.%hhu.%hhu:%hu\n", hostips[i] >> 24, - hostips[i] >> 16, hostips[i] >> 8, hostips[i], PORT); - } - - // you can pass the number of threads you want as the first command arg - threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count(); - if (!(1 <= threads && threads <= 100000)) { - kprintf("\r\e[Kerror: invalid number of threads: %d\n", threads); - exit(1); - } - - // secure the server - // - // pledge() and unveil() let us whitelist which system calls and files - // the server will be allowed to use. this way if it gets hacked, they - // won't be able to do much damage, like compromising the whole server - // - // pledge violations on openbsd are logged nicely to the system logger - // but on linux we need to use a cosmopolitan extension to get details - // although doing that slightly weakens the security pledge() provides - // - // if your operating system doesn't support these security features or - // is too old, then pledge() and unveil() don't consider this an error - // so it works. if security is critical there's a special call to test - // which is npassert(!pledge(0, 0)), and npassert(unveil("", 0) != -1) - __pledge_mode = PLEDGE_PENALTY_RETURN_EPERM; // c. greenbean --strace - unveil("/dev/null", "rw"); - unveil(0, 0); - pledge("stdio inet", 0); - - // initialize our synchronization data structures, which were written - // by mike burrows in a library called *nsync we've tailored for libc - unassert(!pthread_cond_init(&statuscond, 0)); - unassert(!pthread_mutex_init(&statuslock, 0)); - unassert(!pthread_mutex_init(&g_clients.mu, 0)); - unassert(!pthread_cond_init(&g_clients.non_full, 0)); - unassert(!pthread_cond_init(&g_clients.non_empty, 0)); - - // spawn over 9000 worker threads - // - // you don't need weird i/o models, or event driven yoyo pattern code - // to build a massively scalable server. the secret is to use threads - // with tiny stacks. then you can write plain simple imperative code! - // - // we block signals in our worker threads so we won't need messy code - // to spin on eintr. operating systems also deliver signals to random - // threads, and we'd have ctrl-c, etc. be handled by the main thread. - // - // alternatively you can just use signal() instead of sigaction(); it - // uses SA_RESTART because all the syscalls the worker currently uses - // are documented as @restartable which means no EINTR toil is needed - sigset_t block; - sigemptyset(&block); - sigaddset(&block, SIGINT); - sigaddset(&block, SIGHUP); - sigaddset(&block, SIGQUIT); - pthread_attr_t attr; - int pagesz = getauxval(AT_PAGESZ); - unassert(!pthread_attr_init(&attr)); - unassert(!pthread_attr_setstacksize(&attr, 65536)); - unassert(!pthread_attr_setguardsize(&attr, pagesz)); - unassert(!pthread_attr_setsigmask_np(&attr, &block)); - unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); - unassert(!pthread_create(&listener, &attr, ListenWorker, 0)); - pthread_t *th = gc(calloc(threads, sizeof(pthread_t))); - for (i = 0; i < threads; ++i) { - int rc; - ++a_workers; - if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) { - --a_workers; - kprintf("\r\e[Kpthread_create failed: %s\n", strerror(rc)); - if (rc == EAGAIN) { - kprintf("sudo prlimit --pid=$$ --nofile=%d\n", threads * 3); - kprintf("sudo prlimit --pid=$$ --nproc=%d\n", threads * 2); - } - if (!i) exit(1); - threads = i; - break; - } - if (!(i % 50)) { - PrintStatus(); - } - } - unassert(!pthread_attr_destroy(&attr)); - - // wait for workers to terminate - unassert(!pthread_mutex_lock(&statuslock)); - while (!a_termsig) { - PrintStatus(); - unassert(!pthread_cond_wait(&statuscond, &statuslock)); - usleep(10 * 1000); - } - unassert(!pthread_mutex_unlock(&statuslock)); - - // cancel all the worker threads so they shut down asap - // and it'll wait on active clients to gracefully close - // you've never seen a production server close so fast! - close(server); - pthread_cancel(listener); - for (i = 0; i < threads; ++i) { - pthread_cancel(th[i]); - } - - // print status in terminal as the shutdown progresses - unassert(!pthread_mutex_lock(&statuslock)); - while (a_workers) { - unassert(!pthread_cond_wait(&statuscond, &statuslock)); - PrintStatus(); - } - unassert(!pthread_mutex_unlock(&statuslock)); - - // wait for final termination and free thread memory - unassert(!pthread_join(listener, 0)); - for (i = 0; i < threads; ++i) { - unassert(!pthread_join(th[i], 0)); - } - - // clean up terminal line - kprintf("\r\e[Kthank you for choosing \e[32mgreenbean\e[0m\n"); - - // clean up more resources - unassert(!pthread_cond_destroy(&statuscond)); - unassert(!pthread_mutex_destroy(&statuslock)); - unassert(!pthread_mutex_destroy(&g_clients.mu)); - unassert(!pthread_cond_destroy(&g_clients.non_full)); - unassert(!pthread_cond_destroy(&g_clients.non_empty)); - - // quality assurance - if (IsModeDbg()) { - CheckForMemoryLeaks(); - } - - // propagate termination signal - signal(a_termsig, SIG_DFL); - raise(a_termsig); -} diff --git a/libc/calls/mkntpath.c b/libc/calls/mkntpath.c index 60fdd2ae4..b5dff0289 100644 --- a/libc/calls/mkntpath.c +++ b/libc/calls/mkntpath.c @@ -54,6 +54,35 @@ textwindows static const char *FixNtMagicPath(const char *path, return path; } +textwindows size_t __normntpath(char16_t *p, size_t n) { + size_t i, j; + for (j = i = 0; i < n; ++i) { + int c = p[i]; + if (c == '/') { + c = '\\'; + } + if (j > 1 && c == '\\' && p[j - 1] == '\\') { + // matched "^/" or "//" but not "^//" + } else if ((j && p[j - 1] == '\\') && // + c == '.' && // + (i + 1 == n || IsSlash(p[i + 1]))) { + // matched "/./" or "/.$" + i += !(i + 1 == n); + } else if ((j && p[j - 1] == '\\') && // + c == '.' && // + (i + 1 < n && p[i + 1] == '.') && // + (i + 2 == n || IsSlash(p[i + 2]))) { + // matched "/../" or "/..$" + while (j && p[j - 1] == '\\') --j; + while (j && p[j - 1] != '\\') --j; + } else { + p[j++] = c; + } + } + p[j] = 0; + return j; +} + textwindows int __mkntpath(const char *path, char16_t path16[hasatleast PATH_MAX]) { return __mkntpath2(path, path16, -1); @@ -78,7 +107,6 @@ textwindows int __mkntpath(const char *path, */ textwindows int __mkntpath2(const char *path, char16_t path16[hasatleast PATH_MAX], int flags) { - // 1. Need +1 for NUL-terminator // 2. Need +1 for UTF-16 overflow // 3. Need ≥2 for SetCurrentDirectory trailing slash requirement @@ -165,32 +193,7 @@ textwindows int __mkntpath2(const char *path, // normalize path // we need it because \\?\... paths have to be normalized // we don't remove the trailing slash since it is special - size_t i, j; - for (j = i = 0; i < n; ++i) { - int c = p[i]; - if (c == '/') { - c = '\\'; - } - if (j > 1 && c == '\\' && p[j - 1] == '\\') { - // matched "^/" or "//" but not "^//" - } else if ((j && p[j - 1] == '\\') && // - c == '.' && // - (i + 1 == n || IsSlash(p[i + 1]))) { - // matched "/./" or "/.$" - i += !(i + 1 == n); - } else if ((j && p[j - 1] == '\\') && // - c == '.' && // - (i + 1 < n && p[i + 1] == '.') && // - (i + 2 == n || IsSlash(p[i + 2]))) { - // matched "/../" or "/..$" - while (j && p[j - 1] == '\\') --j; - while (j && p[j - 1] != '\\') --j; - } else { - p[j++] = c; - } - } - p[j] = 0; - n = j; + n = __normntpath(p, n); // our path is now stored at `path16` with length `n` n = x + m + n; diff --git a/libc/calls/mkntpathat.c b/libc/calls/mkntpathat.c index f71bed04a..ee562bda2 100644 --- a/libc/calls/mkntpathat.c +++ b/libc/calls/mkntpathat.c @@ -27,37 +27,35 @@ #include "libc/sysv/consts/at.h" #include "libc/sysv/errfuns.h" -static int __mkntpathat_impl(int dirfd, const char *path, int flags, - char16_t file[hasatleast PATH_MAX]) { +static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path, + int flags, + char16_t file[hasatleast PATH_MAX]) { + size_t n; char16_t dir[PATH_MAX]; uint32_t dirlen, filelen; if (!isutf8(path, -1)) return eilseq(); // thwart overlong nul in conversion if ((filelen = __mkntpath2(path, file, flags)) == -1) return -1; if (!filelen) return enoent(); - if (file[0] != u'\\' && dirfd != AT_FDCWD) { // ProTip: \\?\C:\foo - if (!__isfdkind(dirfd, kFdFile)) return ebadf(); - dirlen = GetFinalPathNameByHandle(g_fds.p[dirfd].handle, dir, ARRAYLEN(dir), + if (file[0] != u'\\' && dirhand != AT_FDCWD) { // ProTip: \\?\C:\foo + dirlen = GetFinalPathNameByHandle(dirhand, dir, ARRAYLEN(dir), kNtFileNameNormalized | kNtVolumeNameDos); if (!dirlen) return __winerr(); - if (dirlen + 1 + filelen + 1 > ARRAYLEN(dir)) { - STRACE("path too long: %#.*hs\\%#.*hs", dirlen, dir, filelen, file); - return enametoolong(); - } + if (dirlen + 1 + filelen + 1 > ARRAYLEN(dir)) return enametoolong(); dir[dirlen] = u'\\'; memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t)); - memcpy(file, dir, (dirlen + 1 + filelen + 1) * sizeof(char16_t)); - return dirlen + 1 + filelen; + memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t)); + return __normntpath(file, n); } else { return filelen; } } -int __mkntpathat(int dirfd, const char *path, int flags, - char16_t file[hasatleast PATH_MAX]) { +textwindows int __mkntpathath(int64_t dirhand, const char *path, int flags, + char16_t file[hasatleast PATH_MAX]) { // convert the path. int len; - if ((len = __mkntpathat_impl(dirfd, path, flags, file)) == -1) { + if ((len = __mkntpathath_impl(dirhand, path, flags, file)) == -1) { return -1; } @@ -78,3 +76,16 @@ int __mkntpathat(int dirfd, const char *path, int flags, return len; } + +textwindows int __mkntpathat(int dirfd, const char *path, int flags, + char16_t file[hasatleast PATH_MAX]) { + int64_t dirhand; + if (dirfd == AT_FDCWD) { + dirhand = AT_FDCWD; + } else if (__isfdkind(dirfd, kFdFile)) { + dirhand = g_fds.p[dirfd].handle; + } else { + return ebadf(); + } + return __mkntpathath(dirhand, path, flags, file); +} diff --git a/libc/calls/ntspawn.c b/libc/calls/ntspawn.c index dee93eabb..26014b92d 100644 --- a/libc/calls/ntspawn.c +++ b/libc/calls/ntspawn.c @@ -73,7 +73,7 @@ static void ntspawn_free(void *ptr) { * @asyncsignalsafe */ textwindows int ntspawn( - const char *prog, char *const argv[], char *const envp[], + int64_t dirhand, const char *prog, char *const argv[], char *const envp[], char *const extravars[], uint32_t dwCreationFlags, const char16_t *opt_lpCurrentDirectory, int64_t opt_hParentProcess, int64_t *opt_lpExplicitHandleList, uint32_t dwExplicitHandleCount, @@ -82,7 +82,8 @@ textwindows int ntspawn( int rc = -1; struct SpawnBlock *sb; BLOCK_SIGNALS; - if ((sb = ntspawn_malloc(sizeof(*sb))) && __mkntpath(prog, sb->path) != -1) { + if ((sb = ntspawn_malloc(sizeof(*sb))) && + __mkntpathath(dirhand, prog, 0, sb->path) != -1) { if (!mkntcmdline(sb->cmdline, argv) && !mkntenvblock(sb->envblock, envp, extravars, sb->envbuf)) { bool32 ok; @@ -133,6 +134,8 @@ textwindows int ntspawn( STRACE("CreateProcess() failed w/ %d", GetLastError()); if (GetLastError() == kNtErrorSharingViolation) { etxtbsy(); + } else if (GetLastError() == kNtErrorInvalidName) { + enoent(); } } rc = __fix_enotdir(rc, sb->path); diff --git a/libc/calls/pledge.c b/libc/calls/pledge.c index 74794e647..98a1173ad 100644 --- a/libc/calls/pledge.c +++ b/libc/calls/pledge.c @@ -24,6 +24,7 @@ #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/promises.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/nexgen32e/vendor.internal.h" @@ -267,11 +268,13 @@ int pledge(const char *promises, const char *execpromises) { // if bits are missing in execpromises that exist in promises // then execpromises wouldn't be a monotonic access reduction // this check only matters when exec / execnative are allowed - if ((ipromises & ~iexecpromises) && - (~ipromises & (1ul << PROMISE_EXEC))) { + bool notsubset = ((ipromises & ~iexecpromises) && + (~ipromises & (1ul << PROMISE_EXEC))); + if (notsubset && execpromises) { STRACE("execpromises must be a subset of promises"); rc = einval(); } else { + if (notsubset) iexecpromises = ipromises; rc = sys_pledge_linux(ipromises, __pledge_mode); if (rc > -4096u) errno = -rc, rc = -1; } diff --git a/libc/calls/syscall_support-nt.internal.h b/libc/calls/syscall_support-nt.internal.h index 15c2bef16..9fdaa7d80 100644 --- a/libc/calls/syscall_support-nt.internal.h +++ b/libc/calls/syscall_support-nt.internal.h @@ -10,9 +10,11 @@ bool isregularfile_nt(const char *); bool issymlink_nt(const char *); bool32 ntsetprivilege(int64_t, const char16_t *, uint32_t); char16_t *__create_pipe_name(char16_t *); +size_t __normntpath(char16_t *, size_t); int __mkntpath(const char *, char16_t[hasatleast PATH_MAX]); int __mkntpath2(const char *, char16_t[hasatleast PATH_MAX], int); int __mkntpathat(int, const char *, int, char16_t[hasatleast PATH_MAX]); +int __mkntpathath(int64_t, const char *, int, char16_t[hasatleast PATH_MAX]); int ntaccesscheck(const char16_t *, uint32_t) paramsnonnull(); int sys_pause_nt(void); int64_t __fix_enotdir(int64_t, char16_t *); diff --git a/libc/intrin/createprocess.c b/libc/intrin/createprocess.c index 82bbc64f8..a2fb7eda4 100644 --- a/libc/intrin/createprocess.c +++ b/libc/intrin/createprocess.c @@ -45,7 +45,7 @@ CreateProcess(const char16_t *opt_lpApplicationName, char16_t *lpCommandLine, opt_lpCurrentDirectory, lpStartupInfo, opt_out_lpProcessInformation); if (!ok) __winerr(); - NTTRACE("CreateProcess(%#hs, %#!hs, %s, %s, %hhhd, %u, %p, %#hs, %p, %p) → " + NTTRACE("CreateProcess(%#!hs, %#!hs, %s, %s, %hhhd, %u, %p, %#!hs, %p, %p) → " "%hhhd% m", opt_lpApplicationName, lpCommandLine, DescribeNtSecurityAttributes(opt_lpProcessAttributes), diff --git a/libc/intrin/ulock.c b/libc/intrin/ulock.c index 7adaeb951..c6d7437b5 100644 --- a/libc/intrin/ulock.c +++ b/libc/intrin/ulock.c @@ -39,8 +39,8 @@ int ulock_wait(uint32_t operation, void *addr, uint64_t value, LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → ...", operation, addr, value, timeout_micros); rc = sys_ulock_wait(operation, addr, value, timeout_micros); - STRACE("ulock_wait(%#x, %p, %lx, %u) → %d% m", operation, addr, value, - timeout_micros, rc); + LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → %d% m", operation, addr, value, + timeout_micros, rc); return rc; } @@ -48,7 +48,7 @@ int ulock_wait(uint32_t operation, void *addr, uint64_t value, int ulock_wake(uint32_t operation, void *addr, uint64_t wake_value) { int rc; rc = __syscall3i(operation, (long)addr, wake_value, 0x2000000 | 516); - STRACE("ulock_wake(%#x, %p, %lx) → %s", operation, addr, wake_value, - DescribeErrno(rc)); + LOCKTRACE("ulock_wake(%#x, %p, %lx) → %s", operation, addr, wake_value, + DescribeErrno(rc)); return rc; } diff --git a/libc/isystem/cosmo.h b/libc/isystem/cosmo.h index 462e3aced..4ba2046b3 100644 --- a/libc/isystem/cosmo.h +++ b/libc/isystem/cosmo.h @@ -28,6 +28,7 @@ */ #include "libc/calls/calls.h" +#include "libc/calls/pledge.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timeval.h" #include "libc/cosmo.h" @@ -59,6 +60,7 @@ #include "libc/str/unicode.h" #include "libc/str/utf16.h" #include "libc/sysv/errfuns.h" +#include "net/http/http.h" #ifdef COSMO_ALREADY_DEFINED #undef COSMO_ALREADY_DEFINED diff --git a/libc/proc/execve-nt.greg.c b/libc/proc/execve-nt.greg.c index e2989b0df..9894b6484 100644 --- a/libc/proc/execve-nt.greg.c +++ b/libc/proc/execve-nt.greg.c @@ -35,6 +35,7 @@ #include "libc/proc/describefds.internal.h" #include "libc/proc/ntspawn.h" #include "libc/str/str.h" +#include "libc/sysv/consts/at.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" @@ -85,9 +86,9 @@ textwindows int sys_execve_nt(const char *program, char *const argv[], // launch the process struct NtProcessInformation pi; - int rc = - ntspawn(program, argv, envp, (char *[]){fdspec, 0}, 0, 0, hParentProcess, - lpExplicitHandles, dwExplicitHandleCount, &si, &pi); + int rc = ntspawn(AT_FDCWD, program, argv, envp, (char *[]){fdspec, 0}, 0, 0, + hParentProcess, lpExplicitHandles, dwExplicitHandleCount, + &si, &pi); __undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount); if (rc == -1) { free(fdspec); diff --git a/libc/proc/fork-nt.c b/libc/proc/fork-nt.c index acefcc354..6174035b4 100644 --- a/libc/proc/fork-nt.c +++ b/libc/proc/fork-nt.c @@ -58,6 +58,7 @@ #include "libc/runtime/memtrack.internal.h" #include "libc/runtime/symbols.internal.h" #include "libc/str/str.h" +#include "libc/sysv/consts/at.h" #include "libc/sysv/consts/limits.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" @@ -345,7 +346,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { } #endif NTTRACE("STARTING SPAWN"); - int spawnrc = ntspawn(GetProgramExecutableName(), args, environ, + int spawnrc = ntspawn(AT_FDCWD, GetProgramExecutableName(), args, environ, (char *[]){forkvar, 0}, dwCreationFlags, 0, 0, 0, 0, &startinfo, &procinfo); if (spawnrc != -1) { diff --git a/libc/proc/ntspawn.h b/libc/proc/ntspawn.h index 853355c8f..3ec1c12b2 100644 --- a/libc/proc/ntspawn.h +++ b/libc/proc/ntspawn.h @@ -7,8 +7,8 @@ COSMOPOLITAN_C_START_ int mkntcmdline(char16_t[32767], char *const[]); int mkntenvblock(char16_t[32767], char *const[], char *const[], char[32767]); -int ntspawn(const char *, char *const[], char *const[], char *const[], uint32_t, - const char16_t *, int64_t, int64_t *, uint32_t, +int ntspawn(int64_t, const char *, char *const[], char *const[], char *const[], + uint32_t, const char16_t *, int64_t, int64_t *, uint32_t, const struct NtStartupInfo *, struct NtProcessInformation *); COSMOPOLITAN_C_END_ diff --git a/libc/proc/posix_spawn.c b/libc/proc/posix_spawn.c index 77d6170b1..959f7585f 100644 --- a/libc/proc/posix_spawn.c +++ b/libc/proc/posix_spawn.c @@ -44,6 +44,10 @@ #include "libc/mem/alloca.h" #include "libc/mem/mem.h" #include "libc/nt/createfile.h" +#include "libc/nt/enum/accessmask.h" +#include "libc/nt/enum/creationdisposition.h" +#include "libc/nt/enum/fileflagandattributes.h" +#include "libc/nt/enum/filesharemode.h" #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/enum/startf.h" #include "libc/nt/files.h" @@ -176,15 +180,15 @@ static textwindows errno_t spawnfds_dup2(struct SpawnFds *fds, int fildes, return 0; } -static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int fildes, - const char *path, int oflag, - int mode) { +static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int64_t dirhand, + int fildes, const char *path, + int oflag, int mode) { int64_t h; errno_t err; char16_t path16[PATH_MAX]; uint32_t perm, share, disp, attr; if ((err = spawnfds_ensure(fds, fildes))) return err; - if (__mkntpathat(AT_FDCWD, path, 0, path16) != -1 && + if (__mkntpathath(dirhand, path, 0, path16) != -1 && GetNtOpenFlags(oflag, mode, &perm, &share, &disp, &attr) != -1 && (h = CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0))) { spawnfds_closelater(fds, h); @@ -198,6 +202,39 @@ static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int fildes, } } +static textwindows errno_t spawnfds_chdir(struct SpawnFds *fds, int64_t dirhand, + const char *path, + int64_t *out_dirhand) { + int64_t h; + char16_t path16[PATH_MAX]; + if (__mkntpathath(dirhand, path, 0, path16) != -1 && + (h = CreateFile(path16, kNtFileGenericRead, + kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, + 0, kNtOpenExisting, + kNtFileAttributeNormal | kNtFileFlagBackupSemantics, + 0))) { + spawnfds_closelater(fds, h); + *out_dirhand = h; + return 0; + } else { + return errno; + } +} + +static textwindows errno_t spawnfds_fchdir(struct SpawnFds *fds, int fildes, + int64_t *out_dirhand) { + int64_t h; + if (spawnfds_exists(fds, fildes)) { + h = fds->p[fildes].handle; + } else if (__isfdopen(fildes)) { + h = g_fds.p[fildes].handle; + } else { + return EBADF; + } + *out_dirhand = h; + return 0; +} + static textwindows errno_t posix_spawn_nt_impl( int *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { @@ -207,6 +244,7 @@ static textwindows errno_t posix_spawn_nt_impl( errno_t e = errno; struct Proc *proc = 0; struct SpawnFds fds = {0}; + int64_t dirhand = AT_FDCWD; int64_t *lpExplicitHandles = 0; uint32_t dwExplicitHandleCount = 0; int64_t hCreatorProcess = GetCurrentProcess(); @@ -257,12 +295,27 @@ static textwindows errno_t posix_spawn_nt_impl( } break; case _POSIX_SPAWN_OPEN: - err = spawnfds_open(&fds, a->fildes, a->path, a->oflag, a->mode); + err = spawnfds_open(&fds, dirhand, a->fildes, a->path, a->oflag, + a->mode); if (err) { STRACE("spawnfds_open(%d, %#s) failed", a->fildes, a->path); goto ReturnErr; } break; + case _POSIX_SPAWN_CHDIR: + err = spawnfds_chdir(&fds, dirhand, a->path, &dirhand); + if (err) { + STRACE("spawnfds_chdir(%#s) failed", a->path); + goto ReturnErr; + } + break; + case _POSIX_SPAWN_FCHDIR: + err = spawnfds_fchdir(&fds, a->fildes, &dirhand); + if (err) { + STRACE("spawnfds_fchdir(%d) failed", a->fildes); + goto ReturnErr; + } + break; default: __builtin_unreachable(); } @@ -289,15 +342,26 @@ static textwindows errno_t posix_spawn_nt_impl( .hStdError = spawnfds_handle(&fds, 2), }; + // determine spawn directory + char16_t *lpCurrentDirectory = 0; + if (dirhand != AT_FDCWD) { + lpCurrentDirectory = alloca(PATH_MAX * sizeof(char16_t)); + if (!GetFinalPathNameByHandle(dirhand, lpCurrentDirectory, PATH_MAX, + kNtFileNameNormalized | kNtVolumeNameDos)) { + err = GetLastError(); + goto ReturnErr; + } + } + // launch process int rc = -1; struct NtProcessInformation procinfo; if (!envp) envp = environ; if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess, &lpExplicitHandles, &dwExplicitHandleCount))) { - rc = ntspawn(path, argv, envp, (char *[]){fdspec, 0}, dwCreationFlags, 0, 0, - lpExplicitHandles, dwExplicitHandleCount, &startinfo, - &procinfo); + rc = ntspawn(dirhand, path, argv, envp, (char *[]){fdspec, 0}, + dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles, + dwExplicitHandleCount, &startinfo, &procinfo); } if (rc == -1) { err = errno; @@ -479,6 +543,16 @@ errno_t posix_spawn(int *pid, const char *path, } break; } + case _POSIX_SPAWN_CHDIR: + if (chdir(a->path) == -1) { + goto ChildFailed; + } + break; + case _POSIX_SPAWN_FCHDIR: + if (fchdir(a->fildes) == -1) { + goto ChildFailed; + } + break; default: __builtin_unreachable(); } diff --git a/libc/proc/posix_spawn.h b/libc/proc/posix_spawn.h index df65a2ff6..9bc0cfd93 100644 --- a/libc/proc/posix_spawn.h +++ b/libc/proc/posix_spawn.h @@ -31,6 +31,9 @@ int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *, int); int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *, int, int); int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *, int, const char *, int, unsigned); +int posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t *, + const char *); +int posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *, int); int posix_spawnattr_init(posix_spawnattr_t *); int posix_spawnattr_destroy(posix_spawnattr_t *); diff --git a/libc/proc/posix_spawn.internal.h b/libc/proc/posix_spawn.internal.h index 5d9cfefed..855774be1 100644 --- a/libc/proc/posix_spawn.internal.h +++ b/libc/proc/posix_spawn.internal.h @@ -5,9 +5,11 @@ #include "libc/calls/struct/sigset.h" #include "libc/proc/posix_spawn.h" -#define _POSIX_SPAWN_CLOSE 1 -#define _POSIX_SPAWN_DUP2 2 -#define _POSIX_SPAWN_OPEN 3 +#define _POSIX_SPAWN_CLOSE 1 +#define _POSIX_SPAWN_DUP2 2 +#define _POSIX_SPAWN_OPEN 3 +#define _POSIX_SPAWN_CHDIR 4 +#define _POSIX_SPAWN_FCHDIR 5 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/libc/proc/posix_spawn_file_actions_addchdir_np.c b/libc/proc/posix_spawn_file_actions_addchdir_np.c new file mode 100644 index 000000000..94f40a832 --- /dev/null +++ b/libc/proc/posix_spawn_file_actions_addchdir_np.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/mem/mem.h" +#include "libc/proc/posix_spawn.h" +#include "libc/proc/posix_spawn.internal.h" + +/** + * Add chdir() action to spawn. + * + * @param file_actions was initialized by posix_spawn_file_actions_init() + * @param path will be safely copied + * @return 0 on success, or errno on error + * @raise ENOMEM if insufficient memory was available + */ +int posix_spawn_file_actions_addchdir_np( + posix_spawn_file_actions_t *file_actions, const char *path) { + char *path2; + if (!(path2 = strdup(path))) return ENOMEM; + return __posix_spawn_add_file_action(file_actions, + (struct _posix_faction){ + .action = _POSIX_SPAWN_CHDIR, + .path = path2, + }); +} diff --git a/libc/proc/posix_spawn_file_actions_addfchdir_np.c b/libc/proc/posix_spawn_file_actions_addfchdir_np.c new file mode 100644 index 000000000..5e98e6cc9 --- /dev/null +++ b/libc/proc/posix_spawn_file_actions_addfchdir_np.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/proc/posix_spawn.h" +#include "libc/proc/posix_spawn.internal.h" + +/** + * Add fchdir() action to spawn. + * + * @param path will be safely copied + * @return 0 on success, or errno on error + * @raise ENOMEM if insufficient memory was available + * @raise EBADF if `fildes` is negative + */ +int posix_spawn_file_actions_addfchdir_np( + posix_spawn_file_actions_t *file_actions, int fildes) { + if (fildes < 0) return EBADF; + return __posix_spawn_add_file_action(file_actions, + (struct _posix_faction){ + .action = _POSIX_SPAWN_FCHDIR, + .fildes = fildes, + }); +} diff --git a/libc/proc/posix_spawn_file_actions_addopen.c b/libc/proc/posix_spawn_file_actions_addopen.c index 3f369489c..f84258195 100644 --- a/libc/proc/posix_spawn_file_actions_addopen.c +++ b/libc/proc/posix_spawn_file_actions_addopen.c @@ -29,16 +29,14 @@ * @param fildes is what open() result gets duplicated to * @param path will be safely copied * @return 0 on success, or errno on error - * @raise ENOMEM if we require more vespene gas + * @raise ENOMEM if insufficient memory was available * @raise EBADF if `fildes` is negative - * @raise ENOTSUP if `fildes` isn't 0, 1, or 2 on Windows */ int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *file_actions, int fildes, const char *path, int oflag, unsigned mode) { char *path2; if (fildes < 0) return EBADF; - if (IsWindows() && fildes > 2) return ENOTSUP; if (!(path2 = strdup(path))) return ENOMEM; return __posix_spawn_add_file_action(file_actions, (struct _posix_faction){ diff --git a/libc/thread/pthread_setname_np.c b/libc/thread/pthread_setname_np.c index bf527bb1b..d28df7154 100644 --- a/libc/thread/pthread_setname_np.c +++ b/libc/thread/pthread_setname_np.c @@ -133,7 +133,7 @@ errno_t pthread_setname_np(pthread_t thread, const char *name) { BLOCK_CANCELATION; err = pthread_setname_impl(pt, name); ALLOW_CANCELATION; - STRACE("pthread_setname_np(%d, %s) → %s", _pthread_tid(pt), name, + STRACE("pthread_setname_np(%d, %#s) → %s", _pthread_tid(pt), name, DescribeErrno(err)); return err; } diff --git a/net/http/parsehttpmessage.c b/net/http/parsehttpmessage.c index fd7490bee..aae0fe810 100644 --- a/net/http/parsehttpmessage.c +++ b/net/http/parsehttpmessage.c @@ -57,7 +57,7 @@ void DestroyHttpMessage(struct HttpMessage *r) { * This parser is responsible for determining the length of a message * and slicing the strings inside it. Performance is attained using * perfect hash tables. No memory allocation is performed for normal - * messages. Line folding is forbidden. State persists across calls so + * messagesy. Line folding is forbidden. State persists across calls so * that fragmented messages can be handled efficiently. A limitation on * message size is imposed to make the header data structures smaller. * diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index 110f7acc5..0f0b07077 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -127,6 +127,12 @@ void *Enclave(void *arg) { return 0; // exit } +TEST(pledge, tester) { + SPAWN(fork); + ASSERT_EQ(0, pledge("stdio rpath wpath cpath proc exec", NULL)); + EXITS(0); +} + TEST(pledge, withThreadMemory) { if (IsOpenbsd()) return; // openbsd doesn't allow it, wisely pthread_t worker; diff --git a/test/libc/proc/posix_spawn_test.c b/test/libc/proc/posix_spawn_test.c index 543620541..9a15daf4c 100644 --- a/test/libc/proc/posix_spawn_test.c +++ b/test/libc/proc/posix_spawn_test.c @@ -53,6 +53,7 @@ #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" +#include "libc/x/x.h" #include "third_party/nsync/mu.h" const char kTinyLinuxExit[128] = { @@ -161,6 +162,29 @@ TEST(posix_spawn, pipe) { ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa)); } +TEST(posix_spawn, chdir) { + int ws, pid, p[2]; + char buf[16] = {0}; + char *args[] = {"cocmd.com", "-c", "cat hello.txt", 0}; + char *envs[] = {0}; + posix_spawn_file_actions_t fa; + testlib_extract("/zip/cocmd.com", "cocmd.com", 0755); + ASSERT_SYS(0, 0, mkdir("subdir", 0777)); + ASSERT_SYS(0, 0, xbarf("subdir/hello.txt", "hello\n", -1)); + ASSERT_SYS(0, 0, pipe2(p, O_CLOEXEC)); + ASSERT_EQ(0, posix_spawn_file_actions_init(&fa)); + ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, p[1], 1)); + ASSERT_EQ(0, posix_spawn_file_actions_addchdir_np(&fa, "subdir")); + ASSERT_EQ(0, posix_spawn(&pid, "../cocmd.com", &fa, 0, args, envs)); + ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa)); + ASSERT_SYS(0, 0, close(p[1])); + ASSERT_NE(-1, waitpid(pid, &ws, 0)); + ASSERT_EQ(0, ws); + ASSERT_SYS(0, 6, read(p[0], buf, sizeof(buf))); + ASSERT_STREQ("hello\n", buf); + ASSERT_SYS(0, 0, close(p[0])); +} + _Thread_local atomic_int gotsome; void OhMyGoth(int sig) { diff --git a/test/libc/proc/test.mk b/test/libc/proc/test.mk index e99cb528a..8bed04e07 100644 --- a/test/libc/proc/test.mk +++ b/test/libc/proc/test.mk @@ -64,6 +64,7 @@ o/$(MODE)/test/libc/proc/posix_spawn_test.com.dbg: \ o/$(MODE)/test/libc/proc/posix_spawn_test.o \ o/$(MODE)/test/libc/proc/proc.pkg \ o/$(MODE)/tool/build/echo.com.zip.o \ + o/$(MODE)/tool/build/cocmd.com.zip.o \ o/$(MODE)/test/libc/mem/prog/life.com.zip.o \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/test/libc/proc/life-pe.com.zip.o \