mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
Introduce posix_spawn_file_actions_addchdir_np()
This commit is contained in:
parent
f92ad74e6b
commit
3a1f887928
25 changed files with 446 additions and 748 deletions
|
@ -97,6 +97,8 @@ EXAMPLES_DIRECTDEPS = \
|
||||||
EXAMPLES_DEPS := \
|
EXAMPLES_DEPS := \
|
||||||
$(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x))))
|
$(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x))))
|
||||||
|
|
||||||
|
$(EXAMPLES_OBJS): override CFLAGS += -isystem libc/isystem
|
||||||
|
|
||||||
o/$(MODE)/examples/examples.pkg: \
|
o/$(MODE)/examples/examples.pkg: \
|
||||||
$(EXAMPLES_OBJS) \
|
$(EXAMPLES_OBJS) \
|
||||||
$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)_A).pkg)
|
$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)_A).pkg)
|
||||||
|
|
|
@ -7,6 +7,22 @@
|
||||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||||
╚─────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────*/
|
||||||
#endif
|
#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/assert.h"
|
||||||
#include "libc/atomic.h"
|
#include "libc/atomic.h"
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
|
@ -20,65 +36,42 @@
|
||||||
#include "libc/fmt/itoa.h"
|
#include "libc/fmt/itoa.h"
|
||||||
#include "libc/intrin/kprintf.h"
|
#include "libc/intrin/kprintf.h"
|
||||||
#include "libc/log/log.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/mem/mem.h"
|
||||||
#include "libc/runtime/runtime.h"
|
#include "libc/runtime/runtime.h"
|
||||||
#include "libc/sock/sock.h"
|
#include "libc/sock/sock.h"
|
||||||
#include "libc/sock/struct/sockaddr.h"
|
#include "libc/sock/struct/sockaddr.h"
|
||||||
|
#include "libc/stdio/stdio.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
#include "libc/sysv/consts/af.h"
|
#include "libc/sysv/consts/af.h"
|
||||||
#include "libc/sysv/consts/auxv.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/sig.h"
|
||||||
#include "libc/sysv/consts/so.h"
|
#include "libc/sysv/consts/so.h"
|
||||||
#include "libc/sysv/consts/sock.h"
|
#include "libc/sysv/consts/sock.h"
|
||||||
#include "libc/sysv/consts/sol.h"
|
#include "libc/sysv/consts/sol.h"
|
||||||
#include "libc/sysv/consts/tcp.h"
|
#include "libc/sysv/consts/tcp.h"
|
||||||
|
#include "libc/sysv/consts/timer.h"
|
||||||
#include "libc/thread/thread.h"
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/thread/thread2.h"
|
#include "libc/thread/thread2.h"
|
||||||
#include "net/http/http.h"
|
#include "net/http/http.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview greenbean lightweight threaded web server
|
* @fileoverview greenbean lightweight threaded web server no. 2
|
||||||
*
|
|
||||||
* $ 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
|
|
||||||
*
|
*
|
||||||
|
* 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 PORT 8080
|
||||||
#define KEEPALIVE 30000
|
#define KEEPALIVE 5000
|
||||||
#define LOGGING 1
|
#define LOGGING 1
|
||||||
|
|
||||||
#define STANDARD_RESPONSE_HEADERS \
|
#define STANDARD_RESPONSE_HEADERS \
|
||||||
|
@ -86,21 +79,30 @@
|
||||||
"Referrer-Policy: origin\r\n" \
|
"Referrer-Policy: origin\r\n" \
|
||||||
"Cache-Control: private; max-age=0\r\n"
|
"Cache-Control: private; max-age=0\r\n"
|
||||||
|
|
||||||
int threads;
|
int server;
|
||||||
int alwaysclose;
|
|
||||||
atomic_int a_termsig;
|
atomic_int a_termsig;
|
||||||
atomic_int a_workers;
|
atomic_int a_workers;
|
||||||
atomic_int a_messages;
|
atomic_int a_messages;
|
||||||
atomic_int a_listening;
|
|
||||||
atomic_int a_connections;
|
atomic_int a_connections;
|
||||||
pthread_cond_t statuscond;
|
pthread_cond_t statuscond;
|
||||||
pthread_mutex_t statuslock;
|
pthread_mutex_t statuslock;
|
||||||
const char *volatile status = "";
|
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) {
|
void SomethingHappened(void) {
|
||||||
unassert(!pthread_cond_signal(&statuscond));
|
unassert(!pthread_cond_signal(&statuscond));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performs a guaranteed update of the main thread status line
|
||||||
void SomethingImportantHappened(void) {
|
void SomethingImportantHappened(void) {
|
||||||
unassert(!pthread_mutex_lock(&statuslock));
|
unassert(!pthread_mutex_lock(&statuslock));
|
||||||
unassert(!pthread_cond_signal(&statuscond));
|
unassert(!pthread_cond_signal(&statuscond));
|
||||||
|
@ -108,105 +110,96 @@ void SomethingImportantHappened(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void *Worker(void *id) {
|
void *Worker(void *id) {
|
||||||
int server, yes = 1;
|
pthread_setname_np(pthread_self(), "Worker");
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
// connection loop
|
// connection loop
|
||||||
++a_listening;
|
|
||||||
SomethingImportantHappened();
|
|
||||||
while (!a_termsig) {
|
while (!a_termsig) {
|
||||||
uint32_t clientaddrsize;
|
int client;
|
||||||
|
uint32_t clientsize;
|
||||||
|
int inmsglen, outmsglen;
|
||||||
struct sockaddr_in clientaddr;
|
struct sockaddr_in clientaddr;
|
||||||
int client, inmsglen, outmsglen;
|
char inbuf[1500], outbuf[1500], *p, *q;
|
||||||
char inbuf[512], outbuf[512], *p, *q;
|
|
||||||
|
|
||||||
// musl libc and cosmopolitan libc support a posix thread extension
|
// musl libc and cosmopolitan libc support a posix thread extension
|
||||||
// that makes thread cancelation work much better. your io routines
|
// 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
|
// normal logic rather than needing to push and pop cleanup handler
|
||||||
// functions onto the stack, or worse dealing with async interrupts
|
// functions onto the stack, or worse dealing with async interrupts
|
||||||
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0));
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0));
|
||||||
|
|
||||||
// wait for client connection
|
// wait for client connection
|
||||||
// we don't bother with poll() because this is actually very speedy
|
clientsize = sizeof(clientaddr);
|
||||||
clientaddrsize = sizeof(clientaddr);
|
client = accept(server, (struct sockaddr *)&clientaddr, &clientsize);
|
||||||
client = accept(server, (struct sockaddr *)&clientaddr, &clientaddrsize);
|
|
||||||
|
|
||||||
// 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));
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0));
|
||||||
|
|
||||||
if (client == -1) {
|
if (client == -1) {
|
||||||
if (errno != EAGAIN && errno != ECANCELED) {
|
// accept() errors are generally ephemeral or recoverable
|
||||||
kprintf("\r\e[Kaccept() returned %m\n");
|
// it'd potentially be a good idea to exponential backoff here
|
||||||
if (errno == ENFILE || errno == EMFILE) {
|
if (errno == ECANCELED) continue; // pthread_cancel() was called
|
||||||
goto TooManyFileDescriptors;
|
LOG("accept() returned %m");
|
||||||
}
|
SomethingHappened();
|
||||||
usleep(10000);
|
|
||||||
}
|
|
||||||
continue;
|
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;
|
++a_connections;
|
||||||
|
LOG("%6H accepted connection from %hhu.%hhu.%hhu.%hhu:%hu", clientip >> 24,
|
||||||
|
clientip >> 16, clientip >> 8, clientip, ntohs(clientaddr.sin_port));
|
||||||
SomethingHappened();
|
SomethingHappened();
|
||||||
|
(void)clientip;
|
||||||
|
|
||||||
// message loop
|
// message loop
|
||||||
ssize_t got, sent;
|
ssize_t got, sent;
|
||||||
struct HttpMessage msg;
|
struct HttpMessage msg;
|
||||||
do {
|
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
|
// wait for next http message (non-fragmented required)
|
||||||
// log the incoming http message
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0));
|
||||||
unsigned clientip = ntohl(clientaddr.sin_addr.s_addr);
|
got = read(client, inbuf, sizeof(inbuf));
|
||||||
kprintf("\r\e[K%6P get some %hhu.%hhu.%hhu.%hhu:%hu %#.*s\n",
|
for (int i = 0; i < got; ++i) {
|
||||||
clientip >> 24, clientip >> 16, clientip >> 8, clientip,
|
if (!inbuf[i]) inbuf[i] = 1;
|
||||||
ntohs(clientaddr.sin_port), msg.uri.b - msg.uri.a,
|
}
|
||||||
inbuf + msg.uri.a);
|
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();
|
SomethingHappened();
|
||||||
#endif
|
|
||||||
|
|
||||||
// display hello world html page for http://127.0.0.1:8080/
|
// display hello world html page for http://127.0.0.1:8080/
|
||||||
struct tm tm;
|
struct tm tm;
|
||||||
|
@ -226,9 +219,6 @@ void *Worker(void *id) {
|
||||||
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
||||||
p = stpcpy(p, "\r\nContent-Length: ");
|
p = stpcpy(p, "\r\nContent-Length: ");
|
||||||
p = FormatInt32(p, strlen(q));
|
p = FormatInt32(p, strlen(q));
|
||||||
if (alwaysclose) {
|
|
||||||
p = stpcpy(p, "\r\nConnection: close");
|
|
||||||
}
|
|
||||||
p = stpcpy(p, "\r\n\r\n");
|
p = stpcpy(p, "\r\n\r\n");
|
||||||
p = stpcpy(p, q);
|
p = stpcpy(p, q);
|
||||||
outmsglen = p - outbuf;
|
outmsglen = p - outbuf;
|
||||||
|
@ -247,9 +237,6 @@ void *Worker(void *id) {
|
||||||
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
||||||
p = stpcpy(p, "\r\nContent-Length: ");
|
p = stpcpy(p, "\r\nContent-Length: ");
|
||||||
p = FormatInt32(p, strlen(q));
|
p = FormatInt32(p, strlen(q));
|
||||||
if (alwaysclose) {
|
|
||||||
p = stpcpy(p, "\r\nConnection: close");
|
|
||||||
}
|
|
||||||
p = stpcpy(p, "\r\n\r\n");
|
p = stpcpy(p, "\r\n\r\n");
|
||||||
p = stpcpy(p, q);
|
p = stpcpy(p, q);
|
||||||
outmsglen = p - outbuf;
|
outmsglen = p - outbuf;
|
||||||
|
@ -260,36 +247,29 @@ void *Worker(void *id) {
|
||||||
// amount, then since we sent the content length and checked
|
// amount, then since we sent the content length and checked
|
||||||
// that the client didn't attach a payload, we are so synced
|
// that the client didn't attach a payload, we are so synced
|
||||||
// thus we can safely process more messages
|
// thus we can safely process more messages
|
||||||
} while (!alwaysclose && //
|
} while (got == inmsglen && //
|
||||||
got == inmsglen && //
|
|
||||||
sent == outmsglen && //
|
sent == outmsglen && //
|
||||||
!msg.headers[kHttpContentLength].a &&
|
!msg.headers[kHttpContentLength].a &&
|
||||||
!msg.headers[kHttpTransferEncoding].a &&
|
!msg.headers[kHttpTransferEncoding].a &&
|
||||||
(msg.method == kHttpGet || msg.method == kHttpHead));
|
(msg.method == kHttpGet || msg.method == kHttpHead));
|
||||||
|
|
||||||
DestroyHttpMessage(&msg);
|
DestroyHttpMessage(&msg);
|
||||||
close(client);
|
|
||||||
--a_connections;
|
--a_connections;
|
||||||
SomethingHappened();
|
SomethingHappened();
|
||||||
|
close(client);
|
||||||
}
|
}
|
||||||
--a_listening;
|
|
||||||
|
|
||||||
// inform the parent that this clone has finished
|
|
||||||
CloseWorker:
|
|
||||||
close(server);
|
|
||||||
WorkerFinished:
|
|
||||||
--a_workers;
|
--a_workers;
|
||||||
|
|
||||||
SomethingImportantHappened();
|
SomethingImportantHappened();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintStatus(void) {
|
void PrintEphemeralStatusLine(void) {
|
||||||
kprintf("\r\e[K\e[32mgreenbean\e[0m "
|
kprintf("\r\e[K\e[32mgreenbean\e[0m "
|
||||||
"workers=%d "
|
"workers=%d "
|
||||||
"listening=%d "
|
|
||||||
"connections=%d "
|
"connections=%d "
|
||||||
"messages=%d%s ",
|
"messages=%d%s ",
|
||||||
a_workers, a_listening, a_connections, a_messages, status);
|
a_workers, a_connections, a_messages, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnTerm(int sig) {
|
void OnTerm(int sig) {
|
||||||
|
@ -312,29 +292,38 @@ int main(int argc, char *argv[]) {
|
||||||
unassert(!sigaction(SIGHUP, &sa, 0));
|
unassert(!sigaction(SIGHUP, &sa, 0));
|
||||||
unassert(!sigaction(SIGTERM, &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
|
// 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)) {
|
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);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// caveat emptor microsofties
|
// create listening socket that'll be shared by threads
|
||||||
if (IsWindows()) {
|
int yes = 1;
|
||||||
kprintf("sorry but windows isn't supported by the greenbean demo yet\n"
|
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)};
|
||||||
"because it doesn't support SO_REUSEPORT which is a nice for\n"
|
server = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
"gaining great performance on UNIX systems, with simple code\n"
|
if (server == -1) {
|
||||||
"however windows will work fine if we limit it to one thread\n");
|
perror("socket");
|
||||||
threads = 1; // we're going to make just one web server thread
|
exit(1);
|
||||||
alwaysclose = 1; // don't let client idle, since it'd block others
|
}
|
||||||
|
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
|
// secure the server
|
||||||
|
@ -385,14 +374,16 @@ int main(int argc, char *argv[]) {
|
||||||
unassert(!pthread_attr_setstacksize(&attr, 65536));
|
unassert(!pthread_attr_setstacksize(&attr, 65536));
|
||||||
unassert(!pthread_attr_setguardsize(&attr, pagesz));
|
unassert(!pthread_attr_setguardsize(&attr, pagesz));
|
||||||
unassert(!pthread_attr_setsigmask_np(&attr, &block));
|
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) {
|
for (i = 0; i < threads; ++i) {
|
||||||
int rc;
|
int rc;
|
||||||
++a_workers;
|
++a_workers;
|
||||||
if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) {
|
if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) {
|
||||||
--a_workers;
|
--a_workers;
|
||||||
kprintf("\r\e[Kpthread_create failed: %s\n", strerror(rc));
|
kprintf("pthread_create failed: %s\n", strerror(rc));
|
||||||
if (rc == EAGAIN) {
|
if (rc == EAGAIN) {
|
||||||
|
kprintf("sudo prlimit --pid=$$ --nofile=%d\n", threads * 2);
|
||||||
kprintf("sudo prlimit --pid=$$ --nproc=%d\n", threads * 2);
|
kprintf("sudo prlimit --pid=$$ --nproc=%d\n", threads * 2);
|
||||||
}
|
}
|
||||||
if (!i) exit(1);
|
if (!i) exit(1);
|
||||||
|
@ -400,17 +391,21 @@ int main(int argc, char *argv[]) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!(i % 50)) {
|
if (!(i % 50)) {
|
||||||
PrintStatus();
|
PrintEphemeralStatusLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unassert(!pthread_attr_destroy(&attr));
|
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));
|
unassert(!pthread_mutex_lock(&statuslock));
|
||||||
while (!a_termsig) {
|
while (!a_termsig) {
|
||||||
PrintStatus();
|
PrintEphemeralStatusLine();
|
||||||
unassert(!pthread_cond_wait(&statuscond, &statuslock));
|
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));
|
unassert(!pthread_mutex_unlock(&statuslock));
|
||||||
|
|
||||||
|
@ -421,11 +416,14 @@ int main(int argc, char *argv[]) {
|
||||||
pthread_cancel(th[i]);
|
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
|
// print status in terminal as the shutdown progresses
|
||||||
unassert(!pthread_mutex_lock(&statuslock));
|
unassert(!pthread_mutex_lock(&statuslock));
|
||||||
while (a_workers) {
|
while (a_workers) {
|
||||||
unassert(!pthread_cond_wait(&statuscond, &statuslock));
|
unassert(!pthread_cond_wait(&statuscond, &statuslock));
|
||||||
PrintStatus();
|
PrintEphemeralStatusLine();
|
||||||
}
|
}
|
||||||
unassert(!pthread_mutex_unlock(&statuslock));
|
unassert(!pthread_mutex_unlock(&statuslock));
|
||||||
|
|
||||||
|
@ -434,19 +432,18 @@ int main(int argc, char *argv[]) {
|
||||||
unassert(!pthread_join(th[i], 0));
|
unassert(!pthread_join(th[i], 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close the server socket
|
||||||
|
if (!IsWindows()) close(server);
|
||||||
|
|
||||||
// clean up terminal line
|
// 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
|
// clean up more resources
|
||||||
unassert(!pthread_mutex_destroy(&statuslock));
|
|
||||||
unassert(!pthread_cond_destroy(&statuscond));
|
unassert(!pthread_cond_destroy(&statuscond));
|
||||||
|
unassert(!pthread_mutex_destroy(&statuslock));
|
||||||
|
|
||||||
// quality assurance
|
// quality assurance
|
||||||
if (IsModeDbg()) {
|
if (IsModeDbg()) {
|
||||||
CheckForMemoryLeaks();
|
CheckForMemoryLeaks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// propagate termination signal
|
|
||||||
signal(a_termsig, SIG_DFL);
|
|
||||||
raise(a_termsig);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -54,6 +54,35 @@ textwindows static const char *FixNtMagicPath(const char *path,
|
||||||
return 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,
|
textwindows int __mkntpath(const char *path,
|
||||||
char16_t path16[hasatleast PATH_MAX]) {
|
char16_t path16[hasatleast PATH_MAX]) {
|
||||||
return __mkntpath2(path, path16, -1);
|
return __mkntpath2(path, path16, -1);
|
||||||
|
@ -78,7 +107,6 @@ textwindows int __mkntpath(const char *path,
|
||||||
*/
|
*/
|
||||||
textwindows int __mkntpath2(const char *path,
|
textwindows int __mkntpath2(const char *path,
|
||||||
char16_t path16[hasatleast PATH_MAX], int flags) {
|
char16_t path16[hasatleast PATH_MAX], int flags) {
|
||||||
|
|
||||||
// 1. Need +1 for NUL-terminator
|
// 1. Need +1 for NUL-terminator
|
||||||
// 2. Need +1 for UTF-16 overflow
|
// 2. Need +1 for UTF-16 overflow
|
||||||
// 3. Need ≥2 for SetCurrentDirectory trailing slash requirement
|
// 3. Need ≥2 for SetCurrentDirectory trailing slash requirement
|
||||||
|
@ -165,32 +193,7 @@ textwindows int __mkntpath2(const char *path,
|
||||||
// normalize path
|
// normalize path
|
||||||
// we need it because \\?\... paths have to be normalized
|
// we need it because \\?\... paths have to be normalized
|
||||||
// we don't remove the trailing slash since it is special
|
// we don't remove the trailing slash since it is special
|
||||||
size_t i, j;
|
n = __normntpath(p, n);
|
||||||
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;
|
|
||||||
|
|
||||||
// our path is now stored at `path16` with length `n`
|
// our path is now stored at `path16` with length `n`
|
||||||
n = x + m + n;
|
n = x + m + n;
|
||||||
|
|
|
@ -27,37 +27,35 @@
|
||||||
#include "libc/sysv/consts/at.h"
|
#include "libc/sysv/consts/at.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
|
|
||||||
static int __mkntpathat_impl(int dirfd, const char *path, int flags,
|
static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
|
||||||
char16_t file[hasatleast PATH_MAX]) {
|
int flags,
|
||||||
|
char16_t file[hasatleast PATH_MAX]) {
|
||||||
|
size_t n;
|
||||||
char16_t dir[PATH_MAX];
|
char16_t dir[PATH_MAX];
|
||||||
uint32_t dirlen, filelen;
|
uint32_t dirlen, filelen;
|
||||||
if (!isutf8(path, -1)) return eilseq(); // thwart overlong nul in conversion
|
if (!isutf8(path, -1)) return eilseq(); // thwart overlong nul in conversion
|
||||||
if ((filelen = __mkntpath2(path, file, flags)) == -1) return -1;
|
if ((filelen = __mkntpath2(path, file, flags)) == -1) return -1;
|
||||||
if (!filelen) return enoent();
|
if (!filelen) return enoent();
|
||||||
if (file[0] != u'\\' && dirfd != AT_FDCWD) { // ProTip: \\?\C:\foo
|
if (file[0] != u'\\' && dirhand != AT_FDCWD) { // ProTip: \\?\C:\foo
|
||||||
if (!__isfdkind(dirfd, kFdFile)) return ebadf();
|
dirlen = GetFinalPathNameByHandle(dirhand, dir, ARRAYLEN(dir),
|
||||||
dirlen = GetFinalPathNameByHandle(g_fds.p[dirfd].handle, dir, ARRAYLEN(dir),
|
|
||||||
kNtFileNameNormalized | kNtVolumeNameDos);
|
kNtFileNameNormalized | kNtVolumeNameDos);
|
||||||
if (!dirlen) return __winerr();
|
if (!dirlen) return __winerr();
|
||||||
if (dirlen + 1 + filelen + 1 > ARRAYLEN(dir)) {
|
if (dirlen + 1 + filelen + 1 > ARRAYLEN(dir)) return enametoolong();
|
||||||
STRACE("path too long: %#.*hs\\%#.*hs", dirlen, dir, filelen, file);
|
|
||||||
return enametoolong();
|
|
||||||
}
|
|
||||||
dir[dirlen] = u'\\';
|
dir[dirlen] = u'\\';
|
||||||
memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t));
|
memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t));
|
||||||
memcpy(file, dir, (dirlen + 1 + filelen + 1) * sizeof(char16_t));
|
memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t));
|
||||||
return dirlen + 1 + filelen;
|
return __normntpath(file, n);
|
||||||
} else {
|
} else {
|
||||||
return filelen;
|
return filelen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int __mkntpathat(int dirfd, const char *path, int flags,
|
textwindows int __mkntpathath(int64_t dirhand, const char *path, int flags,
|
||||||
char16_t file[hasatleast PATH_MAX]) {
|
char16_t file[hasatleast PATH_MAX]) {
|
||||||
|
|
||||||
// convert the path.
|
// convert the path.
|
||||||
int len;
|
int len;
|
||||||
if ((len = __mkntpathat_impl(dirfd, path, flags, file)) == -1) {
|
if ((len = __mkntpathath_impl(dirhand, path, flags, file)) == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,3 +76,16 @@ int __mkntpathat(int dirfd, const char *path, int flags,
|
||||||
|
|
||||||
return len;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ static void ntspawn_free(void *ptr) {
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
*/
|
*/
|
||||||
textwindows int ntspawn(
|
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,
|
char *const extravars[], uint32_t dwCreationFlags,
|
||||||
const char16_t *opt_lpCurrentDirectory, int64_t opt_hParentProcess,
|
const char16_t *opt_lpCurrentDirectory, int64_t opt_hParentProcess,
|
||||||
int64_t *opt_lpExplicitHandleList, uint32_t dwExplicitHandleCount,
|
int64_t *opt_lpExplicitHandleList, uint32_t dwExplicitHandleCount,
|
||||||
|
@ -82,7 +82,8 @@ textwindows int ntspawn(
|
||||||
int rc = -1;
|
int rc = -1;
|
||||||
struct SpawnBlock *sb;
|
struct SpawnBlock *sb;
|
||||||
BLOCK_SIGNALS;
|
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) &&
|
if (!mkntcmdline(sb->cmdline, argv) &&
|
||||||
!mkntenvblock(sb->envblock, envp, extravars, sb->envbuf)) {
|
!mkntenvblock(sb->envblock, envp, extravars, sb->envbuf)) {
|
||||||
bool32 ok;
|
bool32 ok;
|
||||||
|
@ -133,6 +134,8 @@ textwindows int ntspawn(
|
||||||
STRACE("CreateProcess() failed w/ %d", GetLastError());
|
STRACE("CreateProcess() failed w/ %d", GetLastError());
|
||||||
if (GetLastError() == kNtErrorSharingViolation) {
|
if (GetLastError() == kNtErrorSharingViolation) {
|
||||||
etxtbsy();
|
etxtbsy();
|
||||||
|
} else if (GetLastError() == kNtErrorInvalidName) {
|
||||||
|
enoent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rc = __fix_enotdir(rc, sb->path);
|
rc = __fix_enotdir(rc, sb->path);
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "libc/calls/syscall-sysv.internal.h"
|
#include "libc/calls/syscall-sysv.internal.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
|
#include "libc/intrin/kprintf.h"
|
||||||
#include "libc/intrin/promises.internal.h"
|
#include "libc/intrin/promises.internal.h"
|
||||||
#include "libc/intrin/strace.internal.h"
|
#include "libc/intrin/strace.internal.h"
|
||||||
#include "libc/nexgen32e/vendor.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
|
// if bits are missing in execpromises that exist in promises
|
||||||
// then execpromises wouldn't be a monotonic access reduction
|
// then execpromises wouldn't be a monotonic access reduction
|
||||||
// this check only matters when exec / execnative are allowed
|
// this check only matters when exec / execnative are allowed
|
||||||
if ((ipromises & ~iexecpromises) &&
|
bool notsubset = ((ipromises & ~iexecpromises) &&
|
||||||
(~ipromises & (1ul << PROMISE_EXEC))) {
|
(~ipromises & (1ul << PROMISE_EXEC)));
|
||||||
|
if (notsubset && execpromises) {
|
||||||
STRACE("execpromises must be a subset of promises");
|
STRACE("execpromises must be a subset of promises");
|
||||||
rc = einval();
|
rc = einval();
|
||||||
} else {
|
} else {
|
||||||
|
if (notsubset) iexecpromises = ipromises;
|
||||||
rc = sys_pledge_linux(ipromises, __pledge_mode);
|
rc = sys_pledge_linux(ipromises, __pledge_mode);
|
||||||
if (rc > -4096u) errno = -rc, rc = -1;
|
if (rc > -4096u) errno = -rc, rc = -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@ bool isregularfile_nt(const char *);
|
||||||
bool issymlink_nt(const char *);
|
bool issymlink_nt(const char *);
|
||||||
bool32 ntsetprivilege(int64_t, const char16_t *, uint32_t);
|
bool32 ntsetprivilege(int64_t, const char16_t *, uint32_t);
|
||||||
char16_t *__create_pipe_name(char16_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 __mkntpath(const char *, char16_t[hasatleast PATH_MAX]);
|
||||||
int __mkntpath2(const char *, char16_t[hasatleast PATH_MAX], int);
|
int __mkntpath2(const char *, char16_t[hasatleast PATH_MAX], int);
|
||||||
int __mkntpathat(int, const char *, int, char16_t[hasatleast PATH_MAX]);
|
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 ntaccesscheck(const char16_t *, uint32_t) paramsnonnull();
|
||||||
int sys_pause_nt(void);
|
int sys_pause_nt(void);
|
||||||
int64_t __fix_enotdir(int64_t, char16_t *);
|
int64_t __fix_enotdir(int64_t, char16_t *);
|
||||||
|
|
|
@ -45,7 +45,7 @@ CreateProcess(const char16_t *opt_lpApplicationName, char16_t *lpCommandLine,
|
||||||
opt_lpCurrentDirectory, lpStartupInfo,
|
opt_lpCurrentDirectory, lpStartupInfo,
|
||||||
opt_out_lpProcessInformation);
|
opt_out_lpProcessInformation);
|
||||||
if (!ok) __winerr();
|
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",
|
"%hhhd% m",
|
||||||
opt_lpApplicationName, lpCommandLine,
|
opt_lpApplicationName, lpCommandLine,
|
||||||
DescribeNtSecurityAttributes(opt_lpProcessAttributes),
|
DescribeNtSecurityAttributes(opt_lpProcessAttributes),
|
||||||
|
|
|
@ -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,
|
LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → ...", operation, addr, value,
|
||||||
timeout_micros);
|
timeout_micros);
|
||||||
rc = sys_ulock_wait(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,
|
LOCKTRACE("ulock_wait(%#x, %p, %lx, %u) → %d% m", operation, addr, value,
|
||||||
timeout_micros, rc);
|
timeout_micros, rc);
|
||||||
return 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 ulock_wake(uint32_t operation, void *addr, uint64_t wake_value) {
|
||||||
int rc;
|
int rc;
|
||||||
rc = __syscall3i(operation, (long)addr, wake_value, 0x2000000 | 516);
|
rc = __syscall3i(operation, (long)addr, wake_value, 0x2000000 | 516);
|
||||||
STRACE("ulock_wake(%#x, %p, %lx) → %s", operation, addr, wake_value,
|
LOCKTRACE("ulock_wake(%#x, %p, %lx) → %s", operation, addr, wake_value,
|
||||||
DescribeErrno(rc));
|
DescribeErrno(rc));
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
|
#include "libc/calls/pledge.h"
|
||||||
#include "libc/calls/struct/timespec.h"
|
#include "libc/calls/struct/timespec.h"
|
||||||
#include "libc/calls/struct/timeval.h"
|
#include "libc/calls/struct/timeval.h"
|
||||||
#include "libc/cosmo.h"
|
#include "libc/cosmo.h"
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
#include "libc/str/unicode.h"
|
#include "libc/str/unicode.h"
|
||||||
#include "libc/str/utf16.h"
|
#include "libc/str/utf16.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
|
#include "net/http/http.h"
|
||||||
|
|
||||||
#ifdef COSMO_ALREADY_DEFINED
|
#ifdef COSMO_ALREADY_DEFINED
|
||||||
#undef COSMO_ALREADY_DEFINED
|
#undef COSMO_ALREADY_DEFINED
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "libc/proc/describefds.internal.h"
|
#include "libc/proc/describefds.internal.h"
|
||||||
#include "libc/proc/ntspawn.h"
|
#include "libc/proc/ntspawn.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
#include "libc/sysv/consts/at.h"
|
||||||
#include "libc/sysv/consts/o.h"
|
#include "libc/sysv/consts/o.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
#include "libc/thread/posixthread.internal.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
|
// launch the process
|
||||||
struct NtProcessInformation pi;
|
struct NtProcessInformation pi;
|
||||||
int rc =
|
int rc = ntspawn(AT_FDCWD, program, argv, envp, (char *[]){fdspec, 0}, 0, 0,
|
||||||
ntspawn(program, argv, envp, (char *[]){fdspec, 0}, 0, 0, hParentProcess,
|
hParentProcess, lpExplicitHandles, dwExplicitHandleCount,
|
||||||
lpExplicitHandles, dwExplicitHandleCount, &si, &pi);
|
&si, &pi);
|
||||||
__undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount);
|
__undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount);
|
||||||
if (rc == -1) {
|
if (rc == -1) {
|
||||||
free(fdspec);
|
free(fdspec);
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
#include "libc/runtime/memtrack.internal.h"
|
#include "libc/runtime/memtrack.internal.h"
|
||||||
#include "libc/runtime/symbols.internal.h"
|
#include "libc/runtime/symbols.internal.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
#include "libc/sysv/consts/at.h"
|
||||||
#include "libc/sysv/consts/limits.h"
|
#include "libc/sysv/consts/limits.h"
|
||||||
#include "libc/sysv/consts/map.h"
|
#include "libc/sysv/consts/map.h"
|
||||||
#include "libc/sysv/consts/prot.h"
|
#include "libc/sysv/consts/prot.h"
|
||||||
|
@ -345,7 +346,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
NTTRACE("STARTING SPAWN");
|
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,
|
(char *[]){forkvar, 0}, dwCreationFlags, 0, 0, 0, 0,
|
||||||
&startinfo, &procinfo);
|
&startinfo, &procinfo);
|
||||||
if (spawnrc != -1) {
|
if (spawnrc != -1) {
|
||||||
|
|
|
@ -7,8 +7,8 @@ COSMOPOLITAN_C_START_
|
||||||
|
|
||||||
int mkntcmdline(char16_t[32767], char *const[]);
|
int mkntcmdline(char16_t[32767], char *const[]);
|
||||||
int mkntenvblock(char16_t[32767], char *const[], char *const[], char[32767]);
|
int mkntenvblock(char16_t[32767], char *const[], char *const[], char[32767]);
|
||||||
int ntspawn(const char *, char *const[], char *const[], char *const[], uint32_t,
|
int ntspawn(int64_t, const char *, char *const[], char *const[], char *const[],
|
||||||
const char16_t *, int64_t, int64_t *, uint32_t,
|
uint32_t, const char16_t *, int64_t, int64_t *, uint32_t,
|
||||||
const struct NtStartupInfo *, struct NtProcessInformation *);
|
const struct NtStartupInfo *, struct NtProcessInformation *);
|
||||||
|
|
||||||
COSMOPOLITAN_C_END_
|
COSMOPOLITAN_C_END_
|
||||||
|
|
|
@ -44,6 +44,10 @@
|
||||||
#include "libc/mem/alloca.h"
|
#include "libc/mem/alloca.h"
|
||||||
#include "libc/mem/mem.h"
|
#include "libc/mem/mem.h"
|
||||||
#include "libc/nt/createfile.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/processcreationflags.h"
|
||||||
#include "libc/nt/enum/startf.h"
|
#include "libc/nt/enum/startf.h"
|
||||||
#include "libc/nt/files.h"
|
#include "libc/nt/files.h"
|
||||||
|
@ -176,15 +180,15 @@ static textwindows errno_t spawnfds_dup2(struct SpawnFds *fds, int fildes,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int fildes,
|
static textwindows errno_t spawnfds_open(struct SpawnFds *fds, int64_t dirhand,
|
||||||
const char *path, int oflag,
|
int fildes, const char *path,
|
||||||
int mode) {
|
int oflag, int mode) {
|
||||||
int64_t h;
|
int64_t h;
|
||||||
errno_t err;
|
errno_t err;
|
||||||
char16_t path16[PATH_MAX];
|
char16_t path16[PATH_MAX];
|
||||||
uint32_t perm, share, disp, attr;
|
uint32_t perm, share, disp, attr;
|
||||||
if ((err = spawnfds_ensure(fds, fildes))) return err;
|
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 &&
|
GetNtOpenFlags(oflag, mode, &perm, &share, &disp, &attr) != -1 &&
|
||||||
(h = CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0))) {
|
(h = CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0))) {
|
||||||
spawnfds_closelater(fds, h);
|
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(
|
static textwindows errno_t posix_spawn_nt_impl(
|
||||||
int *pid, const char *path, const posix_spawn_file_actions_t *file_actions,
|
int *pid, const char *path, const posix_spawn_file_actions_t *file_actions,
|
||||||
const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) {
|
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;
|
errno_t e = errno;
|
||||||
struct Proc *proc = 0;
|
struct Proc *proc = 0;
|
||||||
struct SpawnFds fds = {0};
|
struct SpawnFds fds = {0};
|
||||||
|
int64_t dirhand = AT_FDCWD;
|
||||||
int64_t *lpExplicitHandles = 0;
|
int64_t *lpExplicitHandles = 0;
|
||||||
uint32_t dwExplicitHandleCount = 0;
|
uint32_t dwExplicitHandleCount = 0;
|
||||||
int64_t hCreatorProcess = GetCurrentProcess();
|
int64_t hCreatorProcess = GetCurrentProcess();
|
||||||
|
@ -257,12 +295,27 @@ static textwindows errno_t posix_spawn_nt_impl(
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case _POSIX_SPAWN_OPEN:
|
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) {
|
if (err) {
|
||||||
STRACE("spawnfds_open(%d, %#s) failed", a->fildes, a->path);
|
STRACE("spawnfds_open(%d, %#s) failed", a->fildes, a->path);
|
||||||
goto ReturnErr;
|
goto ReturnErr;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
@ -289,15 +342,26 @@ static textwindows errno_t posix_spawn_nt_impl(
|
||||||
.hStdError = spawnfds_handle(&fds, 2),
|
.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
|
// launch process
|
||||||
int rc = -1;
|
int rc = -1;
|
||||||
struct NtProcessInformation procinfo;
|
struct NtProcessInformation procinfo;
|
||||||
if (!envp) envp = environ;
|
if (!envp) envp = environ;
|
||||||
if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess,
|
if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess,
|
||||||
&lpExplicitHandles, &dwExplicitHandleCount))) {
|
&lpExplicitHandles, &dwExplicitHandleCount))) {
|
||||||
rc = ntspawn(path, argv, envp, (char *[]){fdspec, 0}, dwCreationFlags, 0, 0,
|
rc = ntspawn(dirhand, path, argv, envp, (char *[]){fdspec, 0},
|
||||||
lpExplicitHandles, dwExplicitHandleCount, &startinfo,
|
dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles,
|
||||||
&procinfo);
|
dwExplicitHandleCount, &startinfo, &procinfo);
|
||||||
}
|
}
|
||||||
if (rc == -1) {
|
if (rc == -1) {
|
||||||
err = errno;
|
err = errno;
|
||||||
|
@ -479,6 +543,16 @@ errno_t posix_spawn(int *pid, const char *path,
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_adddup2(posix_spawn_file_actions_t *, int, int);
|
||||||
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *, int,
|
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *, int,
|
||||||
const char *, int, unsigned);
|
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_init(posix_spawnattr_t *);
|
||||||
int posix_spawnattr_destroy(posix_spawnattr_t *);
|
int posix_spawnattr_destroy(posix_spawnattr_t *);
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
#include "libc/calls/struct/sigset.h"
|
#include "libc/calls/struct/sigset.h"
|
||||||
#include "libc/proc/posix_spawn.h"
|
#include "libc/proc/posix_spawn.h"
|
||||||
|
|
||||||
#define _POSIX_SPAWN_CLOSE 1
|
#define _POSIX_SPAWN_CLOSE 1
|
||||||
#define _POSIX_SPAWN_DUP2 2
|
#define _POSIX_SPAWN_DUP2 2
|
||||||
#define _POSIX_SPAWN_OPEN 3
|
#define _POSIX_SPAWN_OPEN 3
|
||||||
|
#define _POSIX_SPAWN_CHDIR 4
|
||||||
|
#define _POSIX_SPAWN_FCHDIR 5
|
||||||
|
|
||||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||||
COSMOPOLITAN_C_START_
|
COSMOPOLITAN_C_START_
|
||||||
|
|
41
libc/proc/posix_spawn_file_actions_addchdir_np.c
Normal file
41
libc/proc/posix_spawn_file_actions_addchdir_np.c
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
39
libc/proc/posix_spawn_file_actions_addfchdir_np.c
Normal file
39
libc/proc/posix_spawn_file_actions_addfchdir_np.c
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
|
@ -29,16 +29,14 @@
|
||||||
* @param fildes is what open() result gets duplicated to
|
* @param fildes is what open() result gets duplicated to
|
||||||
* @param path will be safely copied
|
* @param path will be safely copied
|
||||||
* @return 0 on success, or errno on error
|
* @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 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 posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *file_actions,
|
||||||
int fildes, const char *path, int oflag,
|
int fildes, const char *path, int oflag,
|
||||||
unsigned mode) {
|
unsigned mode) {
|
||||||
char *path2;
|
char *path2;
|
||||||
if (fildes < 0) return EBADF;
|
if (fildes < 0) return EBADF;
|
||||||
if (IsWindows() && fildes > 2) return ENOTSUP;
|
|
||||||
if (!(path2 = strdup(path))) return ENOMEM;
|
if (!(path2 = strdup(path))) return ENOMEM;
|
||||||
return __posix_spawn_add_file_action(file_actions,
|
return __posix_spawn_add_file_action(file_actions,
|
||||||
(struct _posix_faction){
|
(struct _posix_faction){
|
||||||
|
|
|
@ -133,7 +133,7 @@ errno_t pthread_setname_np(pthread_t thread, const char *name) {
|
||||||
BLOCK_CANCELATION;
|
BLOCK_CANCELATION;
|
||||||
err = pthread_setname_impl(pt, name);
|
err = pthread_setname_impl(pt, name);
|
||||||
ALLOW_CANCELATION;
|
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));
|
DescribeErrno(err));
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ void DestroyHttpMessage(struct HttpMessage *r) {
|
||||||
* This parser is responsible for determining the length of a message
|
* This parser is responsible for determining the length of a message
|
||||||
* and slicing the strings inside it. Performance is attained using
|
* and slicing the strings inside it. Performance is attained using
|
||||||
* perfect hash tables. No memory allocation is performed for normal
|
* 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
|
* that fragmented messages can be handled efficiently. A limitation on
|
||||||
* message size is imposed to make the header data structures smaller.
|
* message size is imposed to make the header data structures smaller.
|
||||||
*
|
*
|
||||||
|
|
|
@ -127,6 +127,12 @@ void *Enclave(void *arg) {
|
||||||
return 0; // exit
|
return 0; // exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(pledge, tester) {
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_EQ(0, pledge("stdio rpath wpath cpath proc exec", NULL));
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(pledge, withThreadMemory) {
|
TEST(pledge, withThreadMemory) {
|
||||||
if (IsOpenbsd()) return; // openbsd doesn't allow it, wisely
|
if (IsOpenbsd()) return; // openbsd doesn't allow it, wisely
|
||||||
pthread_t worker;
|
pthread_t worker;
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "libc/testlib/ezbench.h"
|
#include "libc/testlib/ezbench.h"
|
||||||
#include "libc/testlib/testlib.h"
|
#include "libc/testlib/testlib.h"
|
||||||
#include "libc/thread/thread.h"
|
#include "libc/thread/thread.h"
|
||||||
|
#include "libc/x/x.h"
|
||||||
#include "third_party/nsync/mu.h"
|
#include "third_party/nsync/mu.h"
|
||||||
|
|
||||||
const char kTinyLinuxExit[128] = {
|
const char kTinyLinuxExit[128] = {
|
||||||
|
@ -161,6 +162,29 @@ TEST(posix_spawn, pipe) {
|
||||||
ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
|
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;
|
_Thread_local atomic_int gotsome;
|
||||||
|
|
||||||
void OhMyGoth(int sig) {
|
void OhMyGoth(int sig) {
|
||||||
|
|
|
@ -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/posix_spawn_test.o \
|
||||||
o/$(MODE)/test/libc/proc/proc.pkg \
|
o/$(MODE)/test/libc/proc/proc.pkg \
|
||||||
o/$(MODE)/tool/build/echo.com.zip.o \
|
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.com.zip.o \
|
||||||
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \
|
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \
|
||||||
o/$(MODE)/test/libc/proc/life-pe.com.zip.o \
|
o/$(MODE)/test/libc/proc/life-pe.com.zip.o \
|
||||||
|
|
Loading…
Reference in a new issue