Introduce posix_spawn_file_actions_addchdir_np()

This commit is contained in:
Justine Tunney 2023-10-11 20:26:28 -07:00
parent f92ad74e6b
commit 3a1f887928
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
25 changed files with 446 additions and 748 deletions

View file

@ -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)

View file

@ -7,6 +7,22 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#ifdef __COSMOCC__
#define _COSMO_SOURCE
#include <assert.h>
#include <cosmo.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/auxv.h>
#include <sys/socket.h>
#include <time.h>
#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
*
* <!doctype html>
* <title>hello world</title>
* <h1>hello world</h1>
* <p>this is a fun webpage
* <p>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);
}

View file

@ -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 = "<!doctype html>\r\n"
"<title>hello world</title>\r\n"
"<h1>hello world</h1>\r\n"
"<p>this is a fun webpage\r\n"
"<p>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 = "<!doctype html>\r\n"
"<title>404 not found</title>\r\n"
"<h1>404 not found</h1>\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);
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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 *);

View file

@ -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),

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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_

View file

@ -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();
}

View file

@ -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 *);

View file

@ -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_

View file

@ -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,
});
}

View file

@ -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,
});
}

View file

@ -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){

View file

@ -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;
}

View file

@ -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.
*

View file

@ -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;

View file

@ -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) {

View file

@ -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 \