Make improvements

- We now serialize the file descriptor table when spawning / executing
  processes on Windows. This means you can now inherit more stuff than
  just standard i/o. It's needed by bash, which duplicates the console
  to file descriptor #255. We also now do a better job serializing the
  environment variables, so you're less likely to encounter E2BIG when
  using your bash shell. We also no longer coerce environ to uppercase

- execve() on Windows now remotely controls its parent process to make
  them spawn a replacement for itself. Then it'll be able to terminate
  immediately once the spawn succeeds, without having to linger around
  for the lifetime as a shell process for proxying the exit code. When
  process worker thread running in the parent sees the child die, it's
  given a handle to the new child, to replace it in the process table.

- execve() and posix_spawn() on Windows will now provide CreateProcess
  an explicit handle list. This allows us to remove handle locks which
  enables better fork/spawn concurrency, with seriously correct thread
  safety. Other codebases like Go use the same technique. On the other
  hand fork() still favors the conventional WIN32 inheritence approach
  which can be a little bit messy, but is *controlled* by guaranteeing
  perfectly clean slates at both the spawning and execution boundaries

- sigset_t is now 64 bits. Having it be 128 bits was a mistake because
  there's no reason to use that and it's only supported by FreeBSD. By
  using the system word size, signal mask manipulation on Windows goes
  very fast. Furthermore @asyncsignalsafe funcs have been rewritten on
  Windows to take advantage of signal masking, now that it's much more
  pleasant to use.

- All the overlapped i/o code on Windows has been rewritten for pretty
  good signal and cancelation safety. We're now able to ensure overlap
  data structures are cleaned up so long as you don't longjmp() out of
  out of a signal handler that interrupted an i/o operation. Latencies
  are also improved thanks to the removal of lots of "busy wait" code.
  Waits should be optimal for everything except poll(), which shall be
  the last and final demon we slay in the win32 i/o horror show.

- getrusage() on Windows is now able to report RUSAGE_CHILDREN as well
  as RUSAGE_SELF, thanks to aggregation in the process manager thread.
This commit is contained in:
Justine Tunney 2023-10-08 05:36:18 -07:00
parent af7cb3c82f
commit 791f79fcb3
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
382 changed files with 4008 additions and 4511 deletions

View file

@ -89,7 +89,7 @@ ARCH = aarch64
HOSTS ?= pi silicon
else
ARCH = x86_64
HOSTS ?= freebsd rhel7 rhel5 xnu win10 openbsd netbsd
HOSTS ?= freebsd rhel7 xnu win10 openbsd netbsd
endif
ifeq ($(PREFIX),)

View file

@ -628,15 +628,6 @@ static struct Pick PickBlockUnicodeTrue(struct TtyRgb tl, struct TtyRgb tr,
memset(picks, 0x79, sizeof(picks));
PickUnicode(picks, tl, tr, bl, br, tl, tr, bl, br);
i = windex(picks, 96);
if (i >= 88) {
unsigned j;
fprintf(stderr, "uint16_t picks[96] = {");
for (j = 0; j < 96; ++j) {
fprintf(stderr, "%3d,", picks[j]);
}
fprintf(stderr, "}\n");
}
CHECK_LT(i, 88);
return kPicksUnicode[i];
}

View file

@ -1,320 +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 "ape/sections.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/bits.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/xorshift.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/grnd.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/hyperion.h"
#include "third_party/getopt/getopt.internal.h"
#define B 4096
bool isdone;
bool isbinary;
unsigned long count = -1;
uint64_t bcast(uint64_t f(void)) {
unsigned i;
uint64_t x;
for (x = i = 0; i < 8; ++i) {
x <<= 8;
x |= f() & 255;
}
return x;
}
uint64_t randv6(void) {
static int16_t gorp;
gorp = (gorp + 625) & 077777;
return gorp;
}
uint64_t randv7(void) {
static uint32_t randx = 1;
return ((randx = randx * 1103515245 + 12345) >> 16) & 077777;
}
uint64_t zero(void) {
return 0;
}
uint64_t inc(void) {
static uint64_t x;
return x++;
}
uint64_t unixv6(void) {
return bcast(randv6);
}
uint64_t unixv7(void) {
return bcast(randv7);
}
uint64_t ape(void) {
static int i;
if ((i += 8) > _end - __executable_start) i = 8;
return READ64LE(__executable_start + i);
}
uint64_t moby(void) {
static int i;
if ((i += 8) > kMobySize) i = 8;
return READ64LE(kMoby + i);
}
uint64_t knuth(void) {
uint64_t a, b;
static uint64_t x = 1;
x *= 6364136223846793005;
x += 1442695040888963407;
a = x >> 32;
x *= 6364136223846793005;
x += 1442695040888963407;
b = x >> 32;
return a | b << 32;
}
uint64_t rngset64(void) {
static unsigned i;
static uint64_t s;
if (!i) {
s = _rand64();
i = (s + 1) & (511);
}
return MarsagliaXorshift64(&s);
}
uint64_t xorshift64(void) {
static uint64_t s = kMarsagliaXorshift64Seed;
return MarsagliaXorshift64(&s);
}
uint64_t xorshift32(void) {
static uint32_t s = kMarsagliaXorshift32Seed;
uint64_t a = MarsagliaXorshift32(&s);
uint64_t b = MarsagliaXorshift32(&s);
return (uint64_t)a << 32 | b;
}
uint64_t libc(void) {
uint64_t x;
CHECK_EQ(8, getrandom(&x, 8, 0));
return x;
}
uint64_t GetRandom(void) {
uint64_t x;
CHECK_EQ(8, getrandom(&x, 8, 0));
return x;
}
uint32_t python(void) {
#define K 0 // 624 /* wut */
#define N 624
#define M 397
static int index;
static char once;
static uint32_t mt[N];
static const uint32_t mag01[2] = {0, 0x9908b0dfu};
uint32_t y;
int kk;
if (!once) {
char *sp;
ssize_t rc;
uint32_t i, j, k, s[K];
mt[0] = 19650218;
for (i = 1; i < N; i++) {
mt[i] = (1812433253u * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i);
}
if (K) {
for (sp = (char *)s, i = 0; i < sizeof(s); i += rc) {
if ((rc = getrandom(sp + i, sizeof(s) - i, 0)) == -1) {
if (errno != EINTR) abort();
}
}
for (i = 1, j = 0, k = MAX(N, K); k; k--) {
mt[i] =
(mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1664525u)) + s[j] + j;
if (++i >= N) mt[0] = mt[N - 1], i = 1;
if (++j >= K) j = 0;
}
for (k = N - 1; k; k--) {
mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1566083941u)) - i;
if (++i >= N) mt[0] = mt[N - 1], i = 1;
}
mt[0] = 0x80000000;
explicit_bzero(s, sizeof(s));
}
once = 1;
}
if (index >= N) {
for (kk = 0; kk < N - M; kk++) {
y = (mt[kk] & 0x80000000u) | (mt[kk + 1] & 0x7fffffff);
mt[kk] = mt[kk + M] ^ (y >> 1) ^ mag01[y & 1];
}
for (; kk < N - 1; kk++) {
y = (mt[kk] & 0x80000000u) | (mt[kk + 1] & 0x7fffffff);
mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 1];
}
y = (mt[N - 1] & 0x80000000u) | (mt[0] & 0x7fffffffu);
mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ mag01[y & 1];
index = 0;
}
y = mt[index++];
y ^= y >> 11;
y ^= (y << 7) & 0x9d2c5680u;
y ^= (y << 15) & 0xefc60000u;
y ^= y >> 18;
return y;
#undef M
#undef N
#undef K
}
uint64_t pythonx2(void) {
uint64_t x = python();
x <<= 32;
x |= python();
return x;
}
const struct Function {
const char *s;
uint64_t (*f)(void);
} kFunctions[] = {
{"ape", ape}, //
{"getrandom", GetRandom}, //
{"inc", inc}, //
{"knuth", knuth}, //
{"lemur64", lemur64}, //
{"libc", libc}, //
{"moby", moby}, //
{"mt19937", _mt19937}, //
{"python", pythonx2}, //
{"rand64", _rand64}, //
{"rdrand", rdrand}, //
{"rdrnd", rdrand}, //
{"rdseed", rdseed}, //
{"rngset64", rngset64}, //
{"unixv6", unixv6}, //
{"unixv7", unixv7}, //
{"vigna", vigna}, //
{"xorshift32", xorshift32}, //
{"xorshift64", xorshift64}, //
{"zero", zero}, //
};
void OnInt(int sig) {
isdone = true;
}
wontreturn void PrintUsage(FILE *f, int rc) {
fprintf(f, "Usage: %s [-b] [-n NUM] [FUNC]\n", program_invocation_name);
exit(rc);
}
int main(int argc, char *argv[]) {
char *p;
int i, opt;
ssize_t rc;
uint64_t x;
static char buf[B];
uint64_t (*f)(void);
while ((opt = getopt(argc, argv, "hbc:n:")) != -1) {
switch (opt) {
case 'b':
isbinary = true;
break;
case 'c':
case 'n':
count = sizetol(optarg, 1024);
break;
case 'h':
PrintUsage(stdout, EXIT_SUCCESS);
default:
PrintUsage(stderr, EX_USAGE);
}
}
if (optind == argc) {
f = libc;
} else {
for (f = 0, i = 0; i < ARRAYLEN(kFunctions); ++i) {
if (!strcasecmp(argv[optind], kFunctions[i].s)) {
f = kFunctions[i].f;
break;
}
}
if (!f) {
fprintf(stderr, "unknown function: %`'s\n", argv[optind]);
fprintf(stderr, "try: ");
for (i = 0; i < ARRAYLEN(kFunctions); ++i) {
if (i) fprintf(stderr, ", ");
fprintf(stderr, "%s", kFunctions[i].s);
}
fprintf(stderr, "\n");
return 1;
}
}
signal(SIGINT, OnInt);
signal(SIGPIPE, SIG_IGN);
if (!isbinary) {
for (; count && !isdone && !feof(stdout); --count) {
printf("0x%016lx\n", f());
}
fflush(stdout);
return ferror(stdout) ? 1 : 0;
}
while (count && !isdone) {
if (count >= B) {
for (i = 0; i < B / 8; ++i) {
x = f();
p = buf + i * 8;
WRITE64LE(p, x);
}
for (i = 0; i < B; i += rc) {
rc = write(1, buf + i, B - i);
if (rc == -1 && errno == EPIPE) exit(1);
if (rc == -1) perror("write"), exit(1);
}
} else {
x = f();
rc = write(1, &x, MIN(8, count));
}
if (!rc) break;
if (rc == -1 && errno == EPIPE) exit(1);
if (rc == -1) perror("write"), exit(1);
count -= rc;
}
return 0;
}

View file

@ -13,8 +13,10 @@
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/winsize.h"
#include "libc/calls/termios.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
@ -35,11 +37,14 @@
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/prio.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "libc/thread/thread.h"
#include "libc/time/time.h"
#include "libc/x/xasprintf.h"
@ -148,12 +153,8 @@ struct ZipGames {
};
static int frame_;
static int drain_;
static int playfd_;
static int playpid_;
static bool exited_;
static bool timeout_;
static bool resized_;
static size_t vtsize_;
static bool artifacts_;
static long tyn_, txn_;
@ -161,6 +162,9 @@ static struct Frame vf_[2];
static struct Audio audio_;
static const char* inputfn_;
static struct Status status_;
static volatile bool exited_;
static volatile bool timeout_;
static volatile bool resized_;
static struct TtyRgb* ttyrgb_;
static unsigned char *R, *G, *B;
static struct ZipGames zipgames_;
@ -235,12 +239,22 @@ void InitPalette(void) {
}
}
static void WriteStringNow(const char* s) {
ttywrite(STDOUT_FILENO, s, strlen(s));
static ssize_t Write(int fd, const void* p, size_t n) {
int rc;
sigset_t ss, oldss;
sigfillset(&ss);
sigprocmask(SIG_SETMASK, &ss, &oldss);
rc = write(fd, p, n);
sigprocmask(SIG_SETMASK, &oldss, 0);
return rc;
}
static void WriteString(const char* s) {
Write(STDOUT_FILENO, s, strlen(s));
}
void Exit(int rc) {
WriteStringNow("\r\n\e[0m\e[J");
WriteString("\r\n\e[0m\e[J");
if (rc && errno) {
fprintf(stderr, "%s%s\r\n", "error: ", strerror(errno));
}
@ -250,11 +264,19 @@ void Exit(int rc) {
void Cleanup(void) {
ttyraw((enum TtyRawFlags)(-1u));
ttyshowcursor(STDOUT_FILENO);
if (playpid_) kill(playpid_, SIGTERM), pthread_yield();
if (playpid_) {
kill(playpid_, SIGKILL);
close(playfd_);
playfd_ = -1;
}
}
void OnCtrlC(void) {
exited_ = true;
}
void OnTimer(void) {
timeout_ = true; // also sends EINTR to poll()
timeout_ = true;
}
void OnResize(void) {
@ -265,12 +287,11 @@ void OnPiped(void) {
exited_ = true;
}
void OnCtrlC(void) {
drain_ = exited_ = true;
}
void OnSigChld(void) {
exited_ = true, playpid_ = 0;
waitpid(-1, 0, WNOHANG);
close(playfd_);
playpid_ = 0;
playfd_ = -1;
}
void InitFrame(struct Frame* f) {
@ -304,7 +325,8 @@ void GetTermSize(void) {
frame_ = 0;
InitFrame(&vf_[0]);
InitFrame(&vf_[1]);
WriteStringNow("\e[0m\e[H\e[J");
WriteString("\e[0m\e[H\e[J");
resized_ = false;
}
void IoInit(void) {
@ -329,13 +351,14 @@ void SetStatus(const char* fmt, ...) {
status_.wait = FPS / 2;
}
void ReadKeyboard(void) {
ssize_t ReadKeyboard(void) {
int ch;
char b[20];
ssize_t i, rc;
memset(b, -1, sizeof(b));
char b[20] = {0};
if ((rc = read(STDIN_FILENO, b, 16)) != -1) {
if (!rc) exited_ = true;
if (!rc) {
Exit(0);
}
for (i = 0; i < rc; ++i) {
ch = b[i];
if (b[i] == '\e') {
@ -447,6 +470,7 @@ void ReadKeyboard(void) {
}
}
}
return rc;
}
bool HasVideo(struct Frame* f) {
@ -471,26 +495,29 @@ void TransmitVideo(void) {
struct Frame* f;
f = &vf_[frame_];
if (!HasVideo(f)) f = FlipFrameBuffer();
if ((rc = write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) {
if ((rc = Write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) {
f->w += rc;
} else if (errno == EAGAIN) {
// slow teletypewriter
} else if (errno == EPIPE) {
Exit(0);
} else if (errno != EINTR) {
Exit(1);
}
}
void TransmitAudio(void) {
ssize_t rc;
if (!playpid_) return;
if (!audio_.i) return;
if ((rc = write(playfd_, audio_.p, audio_.i * sizeof(short))) != -1) {
if (playfd_ == -1) return;
if ((rc = Write(playfd_, audio_.p, audio_.i * sizeof(short))) != -1) {
rc /= sizeof(short);
memmove(audio_.p, audio_.p + rc, (audio_.i - rc) * sizeof(short));
audio_.i -= rc;
} else if (errno == EPIPE) {
kill(playpid_, SIGKILL);
close(playfd_);
playfd_ = -1;
Exit(0);
} else if (errno != EINTR) {
Exit(1);
}
}
@ -534,36 +561,15 @@ void KeyCountdown(struct Action* a) {
}
void PollAndSynchronize(void) {
struct pollfd fds[3];
do {
errno = 0;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = HasPendingVideo() ? STDOUT_FILENO : -1;
fds[1].events = POLLOUT;
fds[2].fd = HasPendingAudio() ? playfd_ : -1;
fds[2].events = POLLOUT;
if (poll(fds, ARRAYLEN(fds), 1. / FPS * 1e3) != -1) {
if (fds[0].revents & (POLLIN | POLLERR)) ReadKeyboard();
if (fds[1].revents & (POLLOUT | POLLERR)) TransmitVideo();
if (fds[2].revents & (POLLOUT | POLLERR)) TransmitAudio();
} else if (errno != EINTR) {
Exit(1);
}
if (exited_) {
if (drain_) {
while (HasPendingVideo()) {
TransmitVideo();
}
}
Exit(0);
}
if (resized_) {
resized_ = false;
GetTermSize();
break;
if (ReadKeyboard() == -1) {
if (errno != EINTR) Exit(1);
if (exited_) Exit(0);
if (resized_) GetTermSize();
}
} while (!timeout_);
TransmitVideo();
TransmitAudio();
timeout_ = false;
KeyCountdown(&arrow_);
KeyCountdown(&button_);
@ -1701,34 +1707,41 @@ int PlayGame(const char* romfile, const char* opt_tasfile) {
// open speaker
// todo: this needs plenty of work
if ((ffplay = commandvenv("FFPLAY", "ffplay"))) {
devnull = open("/dev/null", O_WRONLY | O_CLOEXEC);
pipe2(pipefds, O_CLOEXEC);
if (!(playpid_ = fork())) {
const char* const args[] = {
"ffplay", "-nodisp", "-loglevel", "quiet", "-fflags",
"nobuffer", "-ac", "1", "-ar", "1789773",
"-f", "s16le", "pipe:", NULL,
};
dup2(pipefds[0], 0);
dup2(devnull, 1);
dup2(devnull, 2);
execv(ffplay, (char* const*)args);
abort();
}
close(pipefds[0]);
playfd_ = pipefds[1];
} else {
fputs("\nWARNING\n\
if (!IsWindows()) {
if ((ffplay = commandvenv("FFPLAY", "ffplay"))) {
devnull = open("/dev/null", O_WRONLY | O_CLOEXEC);
pipe2(pipefds, O_CLOEXEC);
if (!(playpid_ = fork())) {
const char* const args[] = {
ffplay, //
"-nodisp", //
"-loglevel", "quiet", //
"-ac", "1", //
"-ar", "1789773", //
"-f", "s16le", //
"pipe:", //
NULL,
};
dup2(pipefds[0], 0);
dup2(devnull, 1);
dup2(devnull, 2);
execv(ffplay, (char* const*)args);
abort();
}
close(pipefds[0]);
playfd_ = pipefds[1];
} else {
fputs("\nWARNING\n\
\n\
Need `ffplay` command to play audio\n\
Try `sudo apt install ffmpeg` on Linux\n\
You can specify it on `PATH` or in `FFPLAY`\n\
\n\
Press enter to continue without sound: ",
stdout);
fflush(stdout);
GetLine();
stdout);
fflush(stdout);
GetLine();
}
}
// Read the ROM file header

View file

@ -16,7 +16,6 @@
#include "libc/calls/termios.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/intrin/kprintf.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/runtime/runtime.h"
@ -42,14 +41,19 @@ char code[512];
int infd, outfd;
struct winsize wsize;
struct termios oldterm;
volatile bool resized, killed;
volatile bool killed, resized, resurrected;
void OnKilled(int sig) {
killed = true;
}
void OnResize(int sig) {
resized = true;
}
void OnKilled(int sig) {
killed = true;
void OnResurrect(int sig) {
resized = true;
resurrected = true;
}
void RestoreTty(void) {
@ -160,16 +164,20 @@ int main(int argc, char *argv[]) {
int e, c, y, x, n, yn, xn;
infd = 0;
outfd = 1;
/* infd = outfd = open("/dev/tty", O_RDWR); */
signal(SIGTERM, OnKilled);
signal(SIGCONT, OnResize);
signal(SIGWINCH, OnResize);
infd = outfd = open("/dev/tty", O_RDWR);
signal(SIGINT, OnSignalThatWontEintrRead);
sigaction(SIGQUIT,
&(struct sigaction){.sa_handler = OnSignalThatWillEintrRead}, 0);
sigaction(SIGTERM, &(struct sigaction){.sa_handler = OnKilled}, 0);
sigaction(SIGWINCH, &(struct sigaction){.sa_handler = OnResize}, 0);
sigaction(SIGCONT, &(struct sigaction){.sa_handler = OnResurrect}, 0);
EnableRawMode();
GetTtySize();
while (!killed) {
if (resurrected) {
dprintf(outfd, "WE LIVE AGAIN ");
resurrected = false;
}
if (resized) {
dprintf(outfd, "SIGWINCH ");
GetTtySize();

BIN
execve_test_prog1.com Executable file

Binary file not shown.

View file

@ -4,18 +4,18 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define BLOCK_CANCELLATIONS \
do { \
int _CancelState; \
_CancelState = _pthread_block_cancellations()
#define BLOCK_CANCELATION \
do { \
int _CancelState; \
_CancelState = _pthread_block_cancelation()
#define ALLOW_CANCELLATIONS \
_pthread_allow_cancellations(_CancelState); \
} \
#define ALLOW_CANCELATION \
_pthread_allow_cancelation(_CancelState); \
} \
while (0)
int _pthread_block_cancellations(void);
void _pthread_allow_cancellations(int);
int _pthread_block_cancelation(void);
void _pthread_allow_cancelation(int);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -1,19 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_H_
#define COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_H_
#include "libc/calls/struct/sigset.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define BLOCK_SIGNALS \
do { \
sigset_t _SigMask; \
_SigMask = _sigblockall()
#define ALLOW_SIGNALS \
_sigsetmask(_SigMask); \
} \
while (0)
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_BLOCKSIGS_H_ */

View file

@ -1,11 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define BEGIN_BLOCKING_OPERATION (void)0
#define END_BLOCKING_OPERATION (void)0
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_ */

View file

@ -26,6 +26,8 @@
#define _POSIX_MEMLOCK_RANGE _POSIX_VERSION
#define _POSIX_SPAWN _POSIX_VERSION
#define NSIG 64
#define SEEK_SET 0 /* relative to beginning */
#define SEEK_CUR 1 /* relative to current position */
#define SEEK_END 2 /* relative to end */

View file

@ -47,6 +47,7 @@ LIBC_CALLS_A_DIRECTDEPS = \
LIBC_NT_PDH \
LIBC_NT_POWRPROF \
LIBC_NT_PSAPI \
LIBC_NT_SYNCHRONIZATION \
LIBC_NT_WS2_32 \
LIBC_STR \
LIBC_SYSV \

View file

@ -17,32 +17,25 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/errno.h"
#include "libc/nt/synchronization.h"
#include "libc/intrin/atomic.h"
#include "libc/sysv/consts/timer.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#include "third_party/finger/finger.h"
#ifdef __x86_64__
static textwindows int sys_clock_nanosleep_nt_impl(int clock,
struct timespec abs) {
struct timespec abs,
sigset_t waitmask) {
uint32_t msdelay;
struct timespec now;
struct PosixThread *pt = _pthread_self();
for (;;) {
if (sys_clock_gettime_nt(clock, &now)) return -1;
if (timespec_cmp(now, abs) >= 0) return 0;
if (_check_interrupts(0)) return -1;
pt->abort_errno = 0;
pt->pt_flags |= PT_INSEMAPHORE;
WaitForSingleObject(pt->semaphore,
timespec_tomillis(timespec_sub(abs, now)));
pt->pt_flags &= ~PT_INSEMAPHORE;
if (pt->abort_errno) {
errno = pt->abort_errno;
return -1;
}
msdelay = timespec_tomillis(timespec_sub(abs, now));
if (_park_norestart(msdelay, waitmask)) return -1;
}
}
@ -51,16 +44,21 @@ textwindows int sys_clock_nanosleep_nt(int clock, int flags,
struct timespec *rem) {
int rc;
struct timespec abs, now;
sigset_t m = __sig_block();
if (flags & TIMER_ABSTIME) {
abs = *req;
} else {
if (sys_clock_gettime_nt(clock, &now)) return -1;
if ((rc = sys_clock_gettime_nt(clock, &now))) goto BailOut;
abs = timespec_add(now, *req);
}
rc = sys_clock_nanosleep_nt_impl(clock, abs);
rc = sys_clock_nanosleep_nt_impl(clock, abs, m);
if (rc == -1 && rem && errno == EINTR) {
sys_clock_gettime_nt(clock, &now);
*rem = timespec_subz(abs, now);
}
BailOut:
__sig_unblock(m);
return rc;
}
#endif /* __x86_64__ */

View file

@ -36,7 +36,7 @@ static errno_t sys_clock_nanosleep(int clock, int flags,
const struct timespec *req,
struct timespec *rem) {
int e, rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
e = errno;
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
rc = __sys_clock_nanosleep(clock, flags, req, rem);
@ -53,7 +53,7 @@ static errno_t sys_clock_nanosleep(int clock, int flags,
rc = errno;
errno = e;
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
#if 0
STRACE("sys_clock_nanosleep(%s, %s, %s, [%s]) → %d% m",
DescribeClockName(clock), DescribeSleepFlags(flags),
@ -197,7 +197,7 @@ static bool ShouldUseSpinNanosleep(int clock, int flags,
* @raise EINVAL if `flags` has an unrecognized value
* @raise EINVAL if `req->tv_nsec [0,1000000000)`
* @raise ENOSYS on bare metal
* @cancellationpoint
* @cancelationpoint
* @returnserrno
* @norestart
*/

View file

@ -16,43 +16,47 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/errno.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
void sys_fcntl_nt_lock_cleanup(int);
textwindows int sys_close_nt(struct Fd *fd, int fildes) {
int e;
bool ok = true;
if (_weaken(sys_fcntl_nt_lock_cleanup)) {
_weaken(sys_fcntl_nt_lock_cleanup)(fildes);
textwindows int sys_close_nt(int fd, int fildes) {
if (fd + 0u >= g_fds.n) return ebadf();
struct Fd *f = g_fds.p + fd;
switch (f->kind) {
case kFdFile:
void sys_fcntl_nt_lock_cleanup(int);
if (_weaken(sys_fcntl_nt_lock_cleanup)) {
_weaken(sys_fcntl_nt_lock_cleanup)(fildes);
}
if ((f->flags & O_ACCMODE) != O_RDONLY &&
GetFileType(f->handle) == kNtFileTypeDisk) {
// Like Linux, closing a file on Windows doesn't guarantee it is
// immediately synced to disk. But unlike Linux this could cause
// subsequent operations, e.g. unlink() to break w/ access error
FlushFileBuffers(f->handle);
}
break;
case kFdEpoll:
if (_weaken(sys_close_epoll_nt)) {
return _weaken(sys_close_epoll_nt)(fd);
}
break;
case kFdSocket:
if (_weaken(sys_closesocket_nt)) {
return _weaken(sys_closesocket_nt)(g_fds.p + fd);
}
break;
default:
break;
}
if (fd->kind == kFdFile && ((fd->flags & O_ACCMODE) != O_RDONLY &&
GetFileType(fd->handle) == kNtFileTypeDisk)) {
// Like Linux, closing a file on Windows doesn't guarantee it's
// immediately synced to disk. But unlike Linux, this could cause
// subsequent operations, e.g. unlink() to break w/ access error.
e = errno;
FlushFileBuffers(fd->handle);
errno = e;
}
// if this file descriptor is wrapped in a named pipe worker thread
// then we need to close our copy of the worker thread handle. it's
// also required that whatever install a worker use malloc, so free
if (!fd->dontclose) {
if (!CloseHandle(fd->handle)) ok = false;
if (fd->kind == kFdConsole && fd->extra && fd->extra != -1) {
if (!CloseHandle(fd->extra)) ok = false;
}
}
return ok ? 0 : -1;
return CloseHandle(f->handle) ? 0 : __winerr();
}

View file

@ -23,6 +23,7 @@
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
@ -30,6 +31,44 @@
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/errfuns.h"
// for performance reasons we want to avoid holding __fds_lock()
// while sys_close() is happening. this leaves the kernel / libc
// having a temporarily inconsistent state. routines that obtain
// file descriptors the way __zipos_open() does need to retry if
// there's indication this race condition happened.
static int close_impl(int fd) {
if (fd < 0) {
return ebadf();
}
// give kprintf() the opportunity to dup() stderr
if (fd == 2 && _weaken(kloghandle)) {
_weaken(kloghandle)();
}
if (__isfdkind(fd, kFdZip)) {
if (_weaken(__zipos_close)) {
return _weaken(__zipos_close)(fd);
}
if (!IsWindows() && !IsMetal()) {
sys_close(fd);
}
return 0;
}
if (!IsWindows() && !IsMetal()) {
return sys_close(fd);
}
if (IsWindows()) {
return sys_close_nt(fd, fd);
}
return 0;
}
/**
* Closes file descriptor.
*
@ -56,42 +95,8 @@
* @vforksafe
*/
int close(int fd) {
int rc;
if (fd < 0) {
rc = ebadf();
} else {
// helps guarantee stderr log gets duplicated before user closes
if (_weaken(kloghandle)) _weaken(kloghandle)();
// for performance reasons we want to avoid holding __fds_lock()
// while sys_close() is happening. this leaves the kernel / libc
// having a temporarily inconsistent state. routines that obtain
// file descriptors the way __zipos_open() does need to retry if
// there's indication this race condition happened.
if (__isfdkind(fd, kFdZip)) {
rc = _weaken(__zipos_close)(fd);
} else {
if (!IsWindows() && !IsMetal()) {
rc = sys_close(fd);
} else if (IsMetal()) {
rc = 0;
} else {
if (__isfdkind(fd, kFdEpoll)) {
rc = _weaken(sys_close_epoll_nt)(fd);
} else if (__isfdkind(fd, kFdSocket)) {
rc = _weaken(sys_closesocket_nt)(g_fds.p + fd);
} else if (__isfdkind(fd, kFdFile) || //
__isfdkind(fd, kFdConsole) || //
__isfdkind(fd, kFdProcess)) { //
rc = sys_close_nt(g_fds.p + fd, fd);
} else {
rc = eio();
}
}
}
if (!__vforked) {
__releasefd(fd);
}
}
int rc = close_impl(fd);
if (!__vforked) __releasefd(fd);
STRACE("close(%d) → %d% m", fd, rc);
return rc;
}

View file

@ -42,7 +42,7 @@ static bool HasCopyFileRange(void) {
int e;
bool ok;
e = errno;
BLOCK_CANCELLATIONS;
BLOCK_CANCELATION;
if (IsLinux()) {
// We modernize our detection by a few years for simplicity.
// This system call is chosen since it's listed by pledge().
@ -53,7 +53,7 @@ static bool HasCopyFileRange(void) {
} else {
ok = false;
}
ALLOW_CANCELLATIONS;
ALLOW_CANCELATION;
errno = e;
return ok;
}
@ -98,14 +98,14 @@ static void copy_file_range_init(void) {
* @raise EIO if a low-level i/o error happens
* @see sendfile() for seekable socket
* @see splice() for fd pipe
* @cancellationpoint
* @cancelationpoint
*/
ssize_t copy_file_range(int infd, int64_t *opt_in_out_inoffset, int outfd,
int64_t *opt_in_out_outoffset, size_t uptobytes,
uint32_t flags) {
ssize_t rc;
cosmo_once(&g_copy_file_range.once, copy_file_range_init);
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (!g_copy_file_range.ok) {
rc = enosys();
@ -123,7 +123,7 @@ ssize_t copy_file_range(int infd, int64_t *opt_in_out_inoffset, int outfd,
opt_in_out_outoffset, uptobytes, flags);
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("copy_file_range(%d, %s, %d, %s, %'zu, %#x) → %'ld% m", infd,
DescribeInOutInt64(rc, opt_in_out_inoffset), outfd,
DescribeInOutInt64(rc, opt_in_out_outoffset), uptobytes, flags, rc);

View file

@ -1,127 +0,0 @@
/*-*- 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 2020 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/calls/copyfile.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.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/files.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/filetime.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/madv.h"
#include "libc/sysv/consts/o.h"
#include "libc/time/time.h"
static textwindows int sys_copyfile_nt(const char *src, const char *dst,
int flags) {
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Wframe-larger-than="
struct {
char16_t src16[PATH_MAX];
char16_t dst16[PATH_MAX];
} M;
CheckLargeStackAllocation(&M, sizeof(M));
#pragma GCC pop_options
int64_t fhsrc, fhdst;
struct NtFileTime accessed, modified;
if (__mkntpath(src, M.src16) == -1) return -1;
if (__mkntpath(dst, M.dst16) == -1) return -1;
if (CopyFile(M.src16, M.dst16, !!(flags & COPYFILE_NOCLOBBER))) {
if (flags & COPYFILE_PRESERVE_TIMESTAMPS) {
fhsrc = CreateFile(M.src16, kNtFileReadAttributes, kNtFileShareRead, NULL,
kNtOpenExisting, kNtFileAttributeNormal, 0);
fhdst = CreateFile(M.dst16, kNtFileWriteAttributes, kNtFileShareRead,
NULL, kNtOpenExisting, kNtFileAttributeNormal, 0);
if (fhsrc != -1 && fhdst != -1) {
GetFileTime(fhsrc, NULL, &accessed, &modified);
SetFileTime(fhdst, NULL, &accessed, &modified);
}
CloseHandle(fhsrc);
CloseHandle(fhdst);
}
return 0;
} else {
return __winerr();
}
}
static int sys_copyfile(const char *src, const char *dst, int flags) {
struct stat st;
size_t remaining;
ssize_t transferred;
struct timespec amtime[2];
int64_t inoffset, outoffset;
int rc, srcfd, dstfd, oflags, omode;
rc = -1;
if ((srcfd = openat(AT_FDCWD, src, O_RDONLY, 0)) != -1) {
if (fstat(srcfd, &st) != -1) {
omode = st.st_mode & 0777;
oflags = O_WRONLY | O_CREAT;
if (flags & COPYFILE_NOCLOBBER) oflags |= O_EXCL;
if ((dstfd = openat(AT_FDCWD, dst, oflags, omode)) != -1) {
remaining = st.st_size;
ftruncate(dstfd, remaining);
inoffset = 0;
outoffset = 0;
while (remaining &&
(transferred = copy_file_range(
srcfd, &inoffset, dstfd, &outoffset, remaining, 0)) != -1) {
remaining -= transferred;
}
if (!remaining) {
rc = 0;
if (flags & COPYFILE_PRESERVE_TIMESTAMPS) {
amtime[0] = st.st_atim;
amtime[1] = st.st_mtim;
utimensat(dstfd, NULL, amtime, 0);
}
}
rc |= close(dstfd);
}
}
rc |= close(srcfd);
}
return rc;
}
/**
* Copies file.
*
* This implementation goes 2x faster than the `cp` command that comes
* included with most systems since we use the newer copy_file_range()
* system call rather than sendfile().
*
* @param flags may have COPYFILE_PRESERVE_TIMESTAMPS, COPYFILE_NOCLOBBER
* @return 0 on success, or -1 w/ errno
*/
int _copyfile(const char *src, const char *dst, int flags) {
if (!IsWindows() || startswith(src, "/zip/") || startswith(dst, "/zip/")) {
return sys_copyfile(src, dst, flags);
} else {
return sys_copyfile_nt(src, dst, flags);
}
}

View file

@ -1,15 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_COPYFILE_H_
#define COSMOPOLITAN_LIBC_CALLS_COPYFILE_H_
#define COPYFILE_NOCLOBBER 1
#define COPYFILE_PRESERVE_OWNER 2
#define COPYFILE_PRESERVE_TIMESTAMPS 4
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int _copyfile(const char *, const char *, int) paramsnonnull();
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_COPYFILE_H_ */

View file

@ -3,19 +3,19 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int begin_cancellation_point(void);
void end_cancellation_point(int);
int begin_cancelation_point(void);
void end_cancelation_point(int);
#ifndef MODE_DBG
#define BEGIN_CANCELLATION_POINT (void)0
#define END_CANCELLATION_POINT (void)0
#define BEGIN_CANCELATION_POINT (void)0
#define END_CANCELATION_POINT (void)0
#else
#define BEGIN_CANCELLATION_POINT \
#define BEGIN_CANCELATION_POINT \
do { \
int _Cp; \
_Cp = begin_cancellation_point()
#define END_CANCELLATION_POINT \
end_cancellation_point(_Cp); \
_Cp = begin_cancelation_point()
#define END_CANCELATION_POINT \
end_cancelation_point(_Cp); \
} \
while (0)
#endif

View file

@ -31,7 +31,7 @@
* @param mode is octal bits, e.g. 0644 usually
* @return file descriptor, or -1 w/ errno
* @see openat() for further documentation
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe

View file

@ -20,24 +20,26 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/sock/internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
// Implements dup(), dup2(), dup3(), and F_DUPFD for Windows.
textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
int64_t rc, proc, handle;
static textwindows int sys_dup_nt_impl(int oldfd, int newfd, int flags,
int start) {
int64_t rc, handle;
unassert(!(flags & ~O_CLOEXEC));
__fds_lock();
if (!__isfdopen(oldfd) || newfd < -1 ||
(g_fds.p[oldfd].kind != kFdFile && g_fds.p[oldfd].kind != kFdSocket &&
g_fds.p[oldfd].kind != kFdConsole)) {
if (!__isfdopen(oldfd) || newfd < -1) {
__fds_unlock();
return ebadf();
}
@ -54,29 +56,19 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
return -1;
}
if (g_fds.p[newfd].kind) {
sys_close_nt(g_fds.p + newfd, newfd);
bzero(g_fds.p + newfd, sizeof(*g_fds.p));
sys_close_nt(newfd, newfd);
}
}
handle = g_fds.p[oldfd].handle;
proc = GetCurrentProcess();
if (DuplicateHandle(proc, handle, proc, &g_fds.p[newfd].handle, 0, false,
if (DuplicateHandle(GetCurrentProcess(), g_fds.p[oldfd].handle,
GetCurrentProcess(), &handle, 0, true,
kNtDuplicateSameAccess)) {
g_fds.p[newfd].kind = g_fds.p[oldfd].kind;
g_fds.p[newfd].mode = g_fds.p[oldfd].mode;
g_fds.p[newfd].flags = g_fds.p[oldfd].flags & ~O_CLOEXEC;
if (flags & O_CLOEXEC) g_fds.p[newfd].flags |= O_CLOEXEC;
if (g_fds.p[oldfd].kind == kFdSocket && _weaken(_dupsockfd)) {
g_fds.p[newfd].extra =
(intptr_t)_weaken(_dupsockfd)((struct SockFd *)g_fds.p[oldfd].extra);
} else if (g_fds.p[oldfd].kind == kFdConsole) {
unassert(DuplicateHandle(proc, g_fds.p[oldfd].extra, proc,
&g_fds.p[newfd].extra, 0, false,
kNtDuplicateSameAccess));
g_fds.p[newfd] = g_fds.p[oldfd];
g_fds.p[newfd].handle = handle;
if (flags & O_CLOEXEC) {
g_fds.p[newfd].flags |= O_CLOEXEC;
} else {
g_fds.p[newfd].extra = g_fds.p[oldfd].extra;
g_fds.p[newfd].flags &= ~O_CLOEXEC;
}
rc = newfd;
} else {
@ -87,3 +79,11 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
__fds_unlock();
return rc;
}
textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
int rc;
BLOCK_SIGNALS;
rc = sys_dup_nt_impl(oldfd, newfd, flags, start);
ALLOW_SIGNALS;
return rc;
}

View file

@ -17,7 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/fileflagandattributes.h"
@ -28,8 +30,8 @@
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
textwindows int sys_fadvise_nt(int fd, uint64_t offset, uint64_t len,
int advice) {
static textwindows int sys_fadvise_nt_impl(int fd, uint64_t offset,
uint64_t len, int advice) {
int64_t h1, h2;
int rc, flags, mode;
uint32_t perm, share, attr;
@ -85,3 +87,12 @@ textwindows int sys_fadvise_nt(int fd, uint64_t offset, uint64_t len,
return rc;
}
textwindows int sys_fadvise_nt(int fd, uint64_t offset, uint64_t len,
int advice) {
int rc;
BLOCK_SIGNALS;
rc = sys_fadvise_nt_impl(fd, offset, len, advice);
ALLOW_SIGNALS;
return rc;
}

View file

@ -21,6 +21,7 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/flock.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h"
@ -125,11 +126,17 @@ textwindows void sys_fcntl_nt_lock_cleanup(int fd) {
pthread_mutex_unlock(&g_locks.mu);
}
static textwindows int64_t GetfileSize(int64_t handle) {
struct NtByHandleFileInformation wst;
if (!GetFileInformationByHandle(handle, &wst)) return __winerr();
return (wst.nFileSizeHigh + 0ull) << 32 | wst.nFileSizeLow;
}
static textwindows int sys_fcntl_nt_lock(struct Fd *f, int fd, int cmd,
uintptr_t arg) {
uint32_t flags;
struct flock *l;
int64_t pos, off, len, end;
int64_t off, len, end;
struct FileLock *fl, *ft, **flp;
if (!_weaken(malloc)) {
@ -144,16 +151,14 @@ static textwindows int sys_fcntl_nt_lock(struct Fd *f, int fd, int cmd,
case SEEK_SET:
break;
case SEEK_CUR:
pos = 0;
if (SetFilePointerEx(f->handle, 0, &pos, SEEK_CUR)) {
off = pos + off;
} else {
return __winerr();
}
off = f->pointer + off;
break;
case SEEK_END:
off = INT64_MAX - off;
case SEEK_END: {
int64_t size;
if ((size = GetfileSize(f->handle)) == -1) return -1;
off = size - off;
break;
}
default:
return einval();
}
@ -363,6 +368,7 @@ static textwindows int sys_fcntl_nt_setfl(int fd, unsigned *flags,
textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) {
int rc;
BLOCK_SIGNALS;
if (__isfdkind(fd, kFdFile) || //
__isfdkind(fd, kFdSocket) || //
__isfdkind(fd, kFdConsole)) {
@ -397,5 +403,6 @@ textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) {
} else {
rc = ebadf();
}
ALLOW_SIGNALS;
return rc;
}

View file

@ -101,7 +101,7 @@
* @raise EDEADLK if `cmd` was `F_SETLKW` and waiting would deadlock
* @raise EMFILE if `cmd` is `F_DUPFD` or `F_DUPFD_CLOEXEC` and
* `RLIMIT_NOFILE` would be exceeded
* @cancellationpoint when `cmd` is `F_SETLKW` or `F_OFD_SETLKW`
* @cancelationpoint when `cmd` is `F_SETLKW` or `F_OFD_SETLKW`
* @asyncsignalsafe
* @restartable
*/
@ -120,9 +120,9 @@ int fcntl(int fd, int cmd, ...) {
rc = _weaken(__zipos_fcntl)(fd, cmd, arg);
} else if (!IsWindows()) {
if (cmd == F_SETLKW || cmd == F_OFD_SETLKW) {
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
rc = sys_fcntl(fd, cmd, arg, __sys_fcntl_cp);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
} else {
rc = sys_fcntl(fd, cmd, arg, __sys_fcntl);
}

View file

@ -17,15 +17,20 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/files.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
textwindows int sys_fdatasync_nt(int fd, bool fake) {
if (!__isfdopen(fd)) return ebadf();
if (!__isfdkind(fd, kFdFile)) return einval();
if (GetFileType(g_fds.p[fd].handle) != kNtFileTypeDisk) return einval();
if (_check_interrupts(0)) return -1;
if (_check_cancel() == -1) return -1;
if (_check_signal(false) == -1) return -1;
if (fake) return 0;
return FlushFileBuffers(g_fds.p[fd].handle) ? 0 : -1;
return FlushFileBuffers(g_fds.p[fd].handle) ? 0 : __winerr();
}
#endif /* __x86_64__ */

View file

@ -39,13 +39,13 @@
* @raise EIO if an i/o error happened
* @see sync(), fsync(), sync_file_range()
* @see __nosync to secretly disable
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
*/
int fdatasync(int fd) {
int rc;
bool fake = __nosync == 0x5453455454534146;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (__isfdkind(fd, kFdZip)) {
rc = erofs();
} else if (!IsWindows()) {
@ -57,7 +57,7 @@ int fdatasync(int fd) {
} else {
rc = sys_fdatasync_nt(fd, fake);
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("fdatasync%s(%d) → %d% m", fake ? "_fake" : "", fd, rc);
return rc;
}

View file

@ -31,12 +31,12 @@
* @param op can have LOCK_{SH,EX,NB,UN} for shared, exclusive,
* non-blocking, and unlocking
* @return 0 on success, or -1 w/ errno
* @cancellationpoint
* @cancelationpoint
* @restartable
*/
int flock(int fd, int op) {
int rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (!IsWindows()) {
rc = sys_flock(fd, op);
@ -44,7 +44,7 @@ int flock(int fd, int op) {
rc = sys_flock_nt(fd, op);
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("flock(%d, %d) → %d% m", fd, op, rc);
return rc;
}

View file

@ -16,6 +16,8 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
@ -33,6 +35,7 @@ textwindows int sys_fstatat_nt(int dirfd, const char *path, struct stat *st,
int64_t fh;
uint16_t path16[PATH_MAX];
if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1;
BLOCK_SIGNALS;
if ((fh = CreateFile(
path16, kNtFileGenericRead,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0,
@ -46,5 +49,6 @@ textwindows int sys_fstatat_nt(int dirfd, const char *path, struct stat *st,
} else {
rc = __winerr();
}
ALLOW_SIGNALS;
return __fix_enotdir(rc, path16);
}

View file

@ -32,7 +32,7 @@
*
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if /zip path
* @cancellationpoint
* @cancelationpoint
*/
int fstatfs(int fd, struct statfs *sf) {
#pragma GCC push_options
@ -41,7 +41,7 @@ int fstatfs(int fd, struct statfs *sf) {
CheckLargeStackAllocation(&m, sizeof(m));
#pragma GCC pop_options
int rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
rc = enotsup();
@ -55,7 +55,7 @@ int fstatfs(int fd, struct statfs *sf) {
rc = ebadf();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("fstatfs(%d, [%s]) → %d% m", fd, DescribeStatfs(rc, sf));
return rc;
}

View file

@ -39,13 +39,13 @@
* @raise EIO if an i/o error happened
* @see fdatasync(), sync_file_range()
* @see __nosync to secretly disable
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
*/
int fsync(int fd) {
int rc;
bool fake = __nosync == 0x5453455454534146;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (__isfdkind(fd, kFdZip)) {
rc = erofs();
} else if (!IsWindows()) {
@ -57,7 +57,7 @@ int fsync(int fd) {
} else {
rc = sys_fdatasync_nt(fd, fake);
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("fsync%s(%d) → %d% m", fake ? "_fake" : "", fd, rc);
return rc;
}

View file

@ -17,23 +17,17 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/enum/filemovemethod.h"
#include "libc/nt/enum/fileinfobyhandleclass.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/sysv/errfuns.h"
textwindows int sys_ftruncate_nt(int64_t handle, uint64_t length) {
bool32 ok;
int64_t tell;
tell = -1;
if ((ok = SetFilePointerEx(handle, 0, &tell, kNtFileCurrent))) {
ok = SetFilePointerEx(handle, length, NULL, kNtFileBegin) &&
SetEndOfFile(handle);
npassert(SetFilePointerEx(handle, tell, NULL, kNtFileBegin));
}
if (ok) {
if (SetFileInformationByHandle(handle, kNtFileAllocationInfo, &length,
sizeof(length))) {
return 0;
} else if (GetLastError() == kNtErrorAccessDenied) {
return einval(); // ftruncate() doesn't raise EACCES

View file

@ -58,12 +58,12 @@
* @raise EINVAL if `fd` wasn't opened in a writeable mode
* @raise EROFS if `fd` is on a read-only filesystem (e.g. zipos)
* @raise ENOSYS on bare metal
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
*/
int ftruncate(int fd, int64_t length) {
int rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (fd < 0) {
rc = ebadf();
@ -82,7 +82,7 @@ int ftruncate(int fd, int64_t length) {
rc = ebadf();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("ftruncate(%d, %'ld) → %d% m", fd, length, rc);
return rc;
}

View file

@ -16,9 +16,9 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/dce.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/nt/process.h"
#include "libc/nt/ntdll.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/processbasicinformation.h"
@ -31,7 +31,6 @@ textwindows int sys_getppid_nt(void) {
sizeof(ProcessInformation), &gotsize)) &&
gotsize >= sizeof(ProcessInformation) &&
ProcessInformation.InheritedFromUniqueProcessId) {
/* TODO(jart): Fix type mismatch and do we need to close this? */
return ProcessInformation.InheritedFromUniqueProcessId;
}
return GetCurrentProcessId();

View file

@ -23,6 +23,7 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
@ -42,7 +43,6 @@
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/xorshift.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/auxv.h"
@ -181,18 +181,18 @@ ssize_t __getrandom(void *p, size_t n, unsigned f) {
if (IsXnu() || IsOpenbsd()) {
rc = GetRandomBsd(p, n, GetRandomEntropy);
} else {
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
rc = sys_getrandom(p, n, f);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
}
} else if (IsFreebsd() || IsNetbsd()) {
rc = GetRandomBsd(p, n, GetRandomArnd);
} else if (IsMetal()) {
rc = GetRandomMetal(p, n, f);
} else {
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
rc = GetDevUrandom(p, n);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
}
return rc;
}
@ -222,7 +222,7 @@ ssize_t __getrandom(void *p, size_t n, unsigned f) {
* On BSD OSes, this entire process is uninterruptible so be careful
* when using large sizes if interruptibility is needed.
*
* Unlike getentropy() this function is a cancellation point. But it
* Unlike getentropy() this function is a cancelation point. But it
* shouldn't be a problem, unless you're using masked mode, in which
* case extra care must be taken to consider the result.
*
@ -243,7 +243,7 @@ ssize_t __getrandom(void *p, size_t n, unsigned f) {
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EFAULT if the `n` bytes at `p` aren't valid memory
* @raise EINTR if we needed to block and a signal was delivered instead
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe
@ -264,13 +264,13 @@ ssize_t getrandom(void *p, size_t n, unsigned f) {
__attribute__((__constructor__)) static textstartup void getrandom_init(void) {
int e, rc;
if (IsWindows() || IsMetal()) return;
BLOCK_CANCELLATIONS;
BLOCK_CANCELATION;
e = errno;
if (!(rc = sys_getrandom(0, 0, 0))) {
have_getrandom = true;
} else {
errno = e;
}
ALLOW_CANCELLATIONS;
ALLOW_CANCELATION;
STRACE("sys_getrandom(0,0,0) → %d% m", rc);
}

View file

@ -6,8 +6,7 @@
#include "libc/dce.h"
#include "libc/macros.internal.h"
#define kSigactionMinRva 8 /* >SIG_{ERR,DFL,IGN,...} */
#define kSigOpRestartable 1
#define kSigactionMinRva 8 /* >SIG_{ERR,DFL,IGN,...} */
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -22,13 +21,15 @@ int __reservefd(int);
int __reservefd_unlocked(int);
void __releasefd(int);
int __ensurefds(int);
int __ensurefds_unlocked(int);
void __printfds(void);
uint32_t sys_getuid_nt(void);
int __pause_thread(uint32_t);
int __ensurefds_unlocked(int);
void __printfds(struct Fd *, size_t);
int IsWindowsExecutable(int64_t);
int CountConsoleInputBytes(struct Fd *);
int FlushConsoleInputBytes(int64_t);
int CountConsoleInputBytes(void);
int FlushConsoleInputBytes(void);
int64_t GetConsoleInputHandle(void);
int64_t GetConsoleOutputHandle(void);
void InterceptTerminalCommands(const char *, size_t);
forceinline int64_t __getfdhandleactual(int fd) {
return g_fds.p[fd].handle;
@ -42,8 +43,11 @@ forceinline bool __isfdkind(int fd, int kind) {
return 0 <= fd && fd < g_fds.n && g_fds.p[fd].kind == kind;
}
int _check_interrupts(int);
int sys_close_nt(struct Fd *, int);
int _check_signal(bool);
int _check_cancel(void);
int sys_close_nt(int, int);
int _park_norestart(uint32_t, uint64_t);
int _park_restartable(uint32_t, uint64_t);
int sys_openat_metal(int, const char *, int, unsigned);
COSMOPOLITAN_C_END_

View file

@ -18,31 +18,28 @@
*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#ifdef __x86_64__
textwindows int _check_interrupts(int sigops) {
int status;
errno_t err;
struct PosixThread *pt = _pthread_self();
if (_weaken(pthread_testcancel_np) &&
(err = _weaken(pthread_testcancel_np)())) {
goto Interrupted;
}
if (_weaken(__sig_check) && (status = _weaken(__sig_check)())) {
STRACE("syscall interrupted (status=%d, sigops=%d)", status, sigops);
if (status == 2 && (sigops & kSigOpRestartable)) {
STRACE("restarting system call");
return 0;
}
err = EINTR;
Interrupted:
pt->abort_errno = errno = err;
return -1;
textwindows int _check_cancel(void) {
if (_weaken(_pthread_cancel_ack) && //
_pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) &&
atomic_load_explicit(&_pthread_self()->pt_canceled,
memory_order_acquire)) {
return _weaken(_pthread_cancel_ack)();
}
return 0;
}
textwindows int _check_signal(bool restartable) {
int status;
if (!_weaken(__sig_check)) return 0;
if (!(status = _weaken(__sig_check)())) return 0;
if (status == 2 && restartable) return 0;
return eintr();
}
#endif /* __x86_64__ */

View file

@ -91,7 +91,6 @@ static int ioctl_default(int fd, unsigned long request, void *arg) {
static int ioctl_fionread(int fd, uint32_t *arg) {
int rc;
uint32_t cm;
int64_t handle;
if (!IsWindows()) {
return sys_ioctl(fd, FIONREAD, arg);
@ -103,6 +102,10 @@ static int ioctl_fionread(int fd, uint32_t *arg) {
} else {
return _weaken(__winsockerr)();
}
} else if (g_fds.p[fd].kind == kFdConsole) {
int bytes = CountConsoleInputBytes();
*arg = MAX(0, bytes);
return 0;
} else if (GetFileType(handle) == kNtFileTypePipe) {
uint32_t avail;
if (PeekNamedPipe(handle, 0, 0, 0, &avail, 0)) {
@ -113,10 +116,6 @@ static int ioctl_fionread(int fd, uint32_t *arg) {
} else {
return __winerr();
}
} else if (GetConsoleMode(handle, &cm)) {
int bytes = CountConsoleInputBytes(g_fds.p + fd);
*arg = MAX(0, bytes);
return 0;
} else {
return eopnotsupp();
}

View file

@ -1,36 +0,0 @@
/*-*- 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 2022 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/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/sysv/errfuns.h"
bool32 sys_isatty_metal(int fd) {
if (__isfdopen(fd)) {
if (__isfdkind(fd, kFdSerial)) {
return true;
} else {
enotty();
return false;
}
} else {
ebadf();
return false;
}
}

View file

@ -17,14 +17,13 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/nt/console.h"
#include "libc/sysv/errfuns.h"
textwindows bool32 sys_isatty_nt(int fd) {
bool32 sys_isatty(int fd) {
if (__isfdopen(fd)) {
uint32_t mode;
if (GetConsoleMode(g_fds.p[fd].handle, &mode)) {
if (__isfdkind(fd, kFdConsole) || __isfdkind(fd, kFdSerial)) {
return true;
} else {
enotty();

View file

@ -42,10 +42,8 @@ bool32 isatty(int fd) {
if (__isfdkind(fd, kFdZip)) {
enotty();
res = false;
} else if (IsWindows()) {
res = sys_isatty_nt(fd);
} else if (IsMetal()) {
res = sys_isatty_metal(fd);
} else if (IsWindows() || IsMetal()) {
res = sys_isatty(fd);
} else if (!sys_ioctl(fd, TIOCGWINSZ, &ws)) {
res = true;
} else {

View file

@ -67,11 +67,9 @@ textwindows int64_t sys_lseek_nt(int fd, int64_t offset, int whence) {
int filetype = GetFileType(f->handle);
if (filetype != kNtFileTypePipe && filetype != kNtFileTypeChar) {
int64_t res;
pthread_mutex_lock(&f->lock);
if ((res = Seek(f, offset, whence)) != -1) {
f->pointer = res;
}
pthread_mutex_unlock(&f->lock);
return res;
} else {
return espipe();

View file

@ -35,7 +35,7 @@
* with random text on success (and not modified on error)
* @return pointer to template on success, or NULL w/ errno
* @raise EINVAL if template didn't end with XXXXXX
* @cancellationpoint
* @cancelationpoint
*/
char *mkdtemp(char *template) {
int n;

View file

@ -16,23 +16,25 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/proc/ntspawn.h"
#include "libc/mem/mem.h"
#include "libc/proc/ntspawn.h"
#include "libc/str/str.h"
#include "libc/str/thompike.h"
#include "libc/str/utf16.h"
#include "libc/sysv/errfuns.h"
#define APPEND(c) \
do { \
cmdline[k++] = c; \
if (k == ARG_MAX / 2) { \
return e2big(); \
} \
#define APPEND(c) \
do { \
if (k == 32766) { \
return e2big(); \
} \
cmdline[k++] = c; \
} while (0)
static bool NeedsQuotes(const char *s) {
if (!*s) return true;
if (!*s) {
return true;
}
do {
switch (*s) {
case '"':
@ -58,34 +60,27 @@ static inline int IsAlpha(int c) {
// GetDosArgv() or GetDosArgv(). This function does NOT escape
// command interpreter syntax, e.g. $VAR (sh), %VAR% (cmd).
//
// TODO(jart): this needs fuzzing and security review
//
// @param cmdline is output buffer
// @param argv is an a NULL-terminated array of UTF-8 strings
// @return 0 on success, or -1 w/ errno
// @raise E2BIG if everything is too huge
// @see "Everyone quotes command line arguments the wrong way" MSDN
// @see libc/runtime/getdosargv.c
textwindows int mkntcmdline(char16_t cmdline[ARG_MAX / 2], char *const argv[]) {
uint64_t w;
wint_t x, y;
// @asyncsignalsafe
textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) {
int slashes, n;
bool needsquote;
char *ansiargv[2];
size_t i, j, k, s;
if (!argv[0]) {
bzero(ansiargv, sizeof(ansiargv));
argv = ansiargv;
}
for (k = i = 0; argv[i]; ++i) {
if (i) APPEND(u' ');
if ((needsquote = NeedsQuotes(argv[i]))) APPEND(u'"');
for (slashes = j = 0;;) {
x = argv[i][j++] & 255;
wint_t x = argv[i][j++] & 255;
if (x >= 0300) {
n = ThomPikeLen(x);
x = ThomPikeByte(x);
while (--n) {
wint_t y;
if ((y = argv[i][j++] & 255)) {
x = ThomPikeMerge(x, y);
} else {
@ -128,10 +123,9 @@ textwindows int mkntcmdline(char16_t cmdline[ARG_MAX / 2], char *const argv[]) {
APPEND(u'\\');
}
slashes = 0;
w = EncodeUtf16(x);
do {
APPEND(w);
} while ((w >>= 16));
uint32_t w = EncodeUtf16(x);
do APPEND(w);
while ((w >>= 16));
}
}
for (s = 0; s < (slashes << needsquote); ++s) {
@ -141,6 +135,6 @@ textwindows int mkntcmdline(char16_t cmdline[ARG_MAX / 2], char *const argv[]) {
APPEND(u'"');
}
}
cmdline[k] = u'\0';
cmdline[k] = 0;
return 0;
}

View file

@ -16,51 +16,44 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/proc/ntspawn.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/bits.h"
#include "libc/assert.h"
#include "libc/intrin/getenv.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/alloca.h"
#include "libc/mem/arraylist2.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/str/thompike.h"
#include "libc/str/utf16.h"
#include "libc/sysv/errfuns.h"
#define ToUpper(c) ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c))
struct EnvBuilder {
char *buf;
char **var;
int bufi;
int vari;
};
static inline int IsAlpha(int c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
static inline char *StrChr(const char *s, int c) {
for (;; ++s) {
if ((*s & 255) == (c & 255)) return (char *)s;
if (!*s) return 0;
}
}
static textwindows inline int CompareStrings(const char *l, const char *r) {
static textwindows int Compare(const char *l, const char *r) {
int a, b;
size_t i = 0;
while ((a = ToUpper(l[i] & 255)) == (b = ToUpper(r[i] & 255)) && r[i]) ++i;
for (;;) {
a = l[i] & 255;
b = r[i] & 255;
if (a == '=') a = 0;
if (b == '=') b = 0;
if (a != b || !b) break;
++i;
}
return a - b;
}
static textwindows void FixPath(char *path) {
char *p;
// skip over variable name
while (*path++) {
if (path[-1] == '=') {
break;
}
}
// turn colon into semicolon
// unless it already looks like a dos path
for (p = path; *p; ++p) {
@ -69,16 +62,14 @@ static textwindows void FixPath(char *path) {
}
}
// turn \c\... into c:\...
// turn /c/... into c:\...
p = path;
if ((p[0] == '/' || p[0] == '\\') && IsAlpha(p[1]) &&
(p[2] == '/' || p[2] == '\\')) {
if (p[0] == '/' && IsAlpha(p[1]) && p[2] == '/') {
p[0] = p[1];
p[1] = ':';
}
for (; *p; ++p) {
if (p[0] == ';' && (p[1] == '/' || p[1] == '\\') && IsAlpha(p[2]) &&
(p[3] == '/' || p[3] == '\\')) {
if (p[0] == ';' && p[1] == '/' && IsAlpha(p[2]) && p[3] == '/') {
p[1] = p[2];
p[2] = ':';
}
@ -92,34 +83,68 @@ static textwindows void FixPath(char *path) {
}
}
static textwindows void InsertString(char **a, size_t i, const char *s,
char buf[ARG_MAX], size_t *bufi,
bool *have_systemroot) {
char *v;
size_t j, k;
static textwindows int InsertString(struct EnvBuilder *env, const char *str) {
int c, i, cmp;
char *var, *path = 0;
v = StrChr(s, '=');
if (!str) return 0;
// apply fixups to var=/c/...
if (v && v[1] == '/' && IsAlpha(v[2]) && v[3] == '/') {
v = buf + *bufi;
for (k = 0; s[k]; ++k) {
if (*bufi + 1 < ARG_MAX) {
buf[(*bufi)++] = s[k];
// copy key=val to buf
var = env->buf + env->bufi;
do {
c = *str++;
if (env->bufi + 2 > 32767) return e2big();
env->buf[env->bufi++] = c;
if (c == '=' && str[0] == '/' && IsAlpha(str[1]) && str[2] == '/') {
path = env->buf + env->bufi;
}
} while (c);
// fixup key=/c/... → key=c:\...
if (path) FixPath(path);
// append key=val to sorted list using insertion sort technique
for (i = env->vari;; --i) {
if (!i || (cmp = Compare(var, env->var[i - 1])) > 0) {
// insert entry for new key
env->var[i] = var;
env->vari++;
break;
}
if (!cmp) {
// deduplicate preferring latter
env->var[i - 1] = var;
for (; i < env->vari; ++i) {
env->var[i] = env->var[i + 1];
}
break;
}
// sift items right to create empty slot at insertion point
env->var[i] = env->var[i - 1];
}
return 0;
}
static textwindows int InsertStrings(struct EnvBuilder *env,
char *const strs[]) {
if (strs) {
for (int i = 0; strs[i]; ++i) {
if (InsertString(env, strs[i]) == -1) {
return -1;
}
}
if (*bufi < ARG_MAX) {
buf[(*bufi)++] = 0;
FixPath(v);
s = v;
}
return 0;
}
static textwindows int CountStrings(char *const strs[]) {
int n = 0;
if (strs) {
while (*strs++) {
++n;
}
}
// append to sorted list
for (j = i; j > 0 && CompareStrings(s, a[j - 1]) < 0; --j) {
a[j] = a[j - 1];
}
a[j] = (char *)s;
return n;
}
/**
@ -127,76 +152,45 @@ static textwindows void InsertString(char **a, size_t i, const char *s,
*
* This is designed to meet the requirements of CreateProcess().
*
* @param envvars receives sorted double-NUL terminated string list
* @param envblock receives sorted double-NUL terminated string list
* @param envp is an a NULL-terminated array of UTF-8 strings
* @param extravar is a VAR=val string we consider part of envp or NULL
* @return 0 on success, or -1 w/ errno
* @error E2BIG if total number of shorts exceeded ARG_MAX/2 (32767)
* @error E2BIG if total number of shorts (including nul) exceeded 32767
* @asyncsignalsafe
*/
textwindows int mkntenvblock(char16_t envvars[ARG_MAX / 2], char *const envp[],
const char *extravar, char buf[ARG_MAX]) {
bool v;
uint64_t w;
char **vars;
wint_t x, y;
bool have_systemroot = false;
size_t i, j, k, n, m, bufi = 0;
for (n = 0; envp[n];) n++;
textwindows int mkntenvblock(char16_t envblock[32767], char *const envp[],
char *const extravars[], char buf[32767]) {
int i, k, n;
struct Env e;
struct EnvBuilder env = {buf};
// allocate string pointer array for sorting purposes
n = (CountStrings(envp) + CountStrings(extravars) + 1) * sizeof(char *);
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Walloca-larger-than="
int nbytes = (n + 1) * sizeof(char *);
vars = alloca(nbytes);
CheckLargeStackAllocation(vars, nbytes);
env.var = alloca(n);
CheckLargeStackAllocation(env.var, n);
#pragma GCC pop_options
for (i = 0; i < n; ++i) {
InsertString(vars, i, envp[i], buf, &bufi, &have_systemroot);
}
if (extravar) {
InsertString(vars, n++, extravar, buf, &bufi, &have_systemroot);
}
if (!have_systemroot && environ) {
// load new environment into string pointer array and fix file paths
if (InsertStrings(&env, envp) == -1) return -1;
if (InsertStrings(&env, extravars) == -1) return -1;
if (environ) {
// https://jpassing.com/2009/12/28/the-hidden-danger-of-forgetting-to-specify-systemroot-in-a-custom-environment-block/
struct Env systemroot;
systemroot = __getenv(environ, "SYSTEMROOT");
if (systemroot.s) {
InsertString(vars, n++, environ[systemroot.i], buf, &bufi,
&have_systemroot);
e = __getenv(environ, "SYSTEMROOT");
if (e.s && InsertString(&env, environ[e.i]) == -1) {
return -1;
}
}
for (k = i = 0; i < n; ++i) {
j = 0;
v = false;
do {
x = vars[i][j++] & 0xff;
if (x >= 0200) {
if (x < 0300) continue;
m = ThomPikeLen(x);
x = ThomPikeByte(x);
while (--m) {
if ((y = vars[i][j++] & 0xff)) {
x = ThomPikeMerge(x, y);
} else {
x = 0;
break;
}
}
}
if (!v) {
if (x != '=') {
x = ToUpper(x);
} else {
v = true;
}
}
w = EncodeUtf16(x);
do {
envvars[k++] = w & 0xffff;
if (k == ARG_MAX / 2) {
return e2big();
}
} while ((w >>= 16));
} while (x);
// copy utf-8 sorted string pointer array into contiguous utf-16 block
// in other words, we're creating a double-nul terminated string list!
for (k = i = 0; i < env.vari; ++i) {
k += tprecode8to16(envblock + k, -1, env.var[i]).ax + 1;
}
envvars[k] = u'\0';
unassert(k <= env.bufi);
envblock[k] = 0;
return 0;
}

View file

@ -34,7 +34,7 @@
* @see mkstemp() if you don't need flags
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
* @cancellationpoint
* @cancelationpoint
*/
int mkostemp(char *template, unsigned flags) {
return openatemp(AT_FDCWD, template, 0, flags, 0);

View file

@ -35,7 +35,7 @@
* @see mkostemp() if you don't need suffix
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
* @cancellationpoint
* @cancelationpoint
*/
int mkostemps(char *template, int suffixlen, unsigned flags) {
return openatemp(AT_FDCWD, template, suffixlen, flags, 0);

View file

@ -34,7 +34,7 @@
* @see mkstemps() if you you need a suffix
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
* @cancellationpoint
* @cancelationpoint
*/
int mkstemp(char *template) {
return openatemp(AT_FDCWD, template, 0, 0, 0);

View file

@ -36,7 +36,7 @@
* @see mkstemp() if you don't need `suffixlen`
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
* @cancellationpoint
* @cancelationpoint
*/
int mkstemps(char *template, int suffixlen) {
return openatemp(AT_FDCWD, template, suffixlen, 0, 0);

View file

@ -36,7 +36,7 @@
* @see mkstemps() if you you need a file extension
* @see openatemp() for one temp roller to rule them all
* @see mkostemp() if you you need a `O_CLOEXEC`, `O_APPEND`, etc.
* @cancellationpoint
* @cancelationpoint
*/
char *mktemp(char *template) {
int fd;

View file

@ -33,7 +33,7 @@
* @raise EFAULT if `req` is NULL or `req` / `rem` is a bad pointer
* @raise ENOSYS on bare metal
* @see clock_nanosleep()
* @cancellationpoint
* @cancelationpoint
* @norestart
*/
int nanosleep(const struct timespec *req, struct timespec *rem) {

View file

@ -19,6 +19,8 @@
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
@ -67,6 +69,7 @@ textwindows int ntaccesscheck(const char16_t *pathname, uint32_t flags) {
struct NtByHandleFileInformation wst;
int64_t hToken, hImpersonatedToken, hFile;
intptr_t buffer[1024 / sizeof(intptr_t)];
BLOCK_SIGNALS;
if (flags & X_OK) flags |= R_OK;
granted = 0;
result = false;
@ -148,5 +151,6 @@ textwindows int ntaccesscheck(const char16_t *pathname, uint32_t flags) {
if (hToken != -1) {
CloseHandle(hToken);
}
ALLOW_SIGNALS;
return rc;
}

161
libc/calls/ntspawn.c Normal file
View file

@ -0,0 +1,161 @@
/*-*- 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 2021 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/proc/ntspawn.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/enum/processaccess.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
#include "libc/nt/memory.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/startupinfo.h"
#include "libc/nt/struct/processinformation.h"
#include "libc/nt/struct/procthreadattributelist.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/nt/struct/startupinfoex.h"
#include "libc/proc/ntspawn.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
struct SpawnBlock {
char16_t path[PATH_MAX];
char16_t cmdline[32767];
char16_t envblock[32767];
char envbuf[32767];
};
static void *ntspawn_malloc(size_t size) {
return HeapAlloc(GetProcessHeap(), 0, size);
}
static void ntspawn_free(void *ptr) {
HeapFree(GetProcessHeap(), 0, ptr);
}
/**
* Spawns process on Windows NT.
*
* This function delegates to CreateProcess() with UTF-8 UTF-16
* translation and argv escaping. Please note this will NOT escape
* command interpreter syntax.
*
* @param prog won't be PATH searched
* @param argv specifies prog arguments
* @param envp[𝟶,m-2] specifies "foo=bar" environment variables, which
* don't need to be passed in sorted order; however, this function
* goes faster the closer they are to sorted
* @param envp[m-1] is NULL
* @param extravars is added to envp to avoid setenv() in caller
* @param opt_out_lpProcessInformation can be used to return process and
* thread IDs to parent, as well as open handles that need close()
* @return 0 on success, or -1 w/ errno
* @see spawnve() which abstracts this function
* @asyncsignalsafe
*/
textwindows int ntspawn(
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,
const struct NtStartupInfo *lpStartupInfo,
struct NtProcessInformation *opt_out_lpProcessInformation) {
int rc = -1;
struct SpawnBlock *sb;
BLOCK_SIGNALS;
if ((sb = ntspawn_malloc(sizeof(*sb))) && __mkntpath(prog, sb->path) != -1) {
if (!mkntcmdline(sb->cmdline, argv) &&
!mkntenvblock(sb->envblock, envp, extravars, sb->envbuf)) {
bool32 ok;
int64_t dp = GetCurrentProcess();
// create attribute list
// this code won't call malloc in practice
void *freeme = 0;
_Alignas(16) char memory[128];
size_t size = sizeof(memory);
struct NtProcThreadAttributeList *alist = (void *)memory;
uint32_t items = !!opt_hParentProcess + !!opt_lpExplicitHandleList;
ok = InitializeProcThreadAttributeList(alist, items, 0, &size);
if (!ok && GetLastError() == kNtErrorInsufficientBuffer) {
ok = !!(alist = freeme = ntspawn_malloc(size));
if (ok) {
ok = InitializeProcThreadAttributeList(alist, items, 0, &size);
}
}
if (ok && opt_hParentProcess) {
ok = UpdateProcThreadAttribute(
alist, 0, kNtProcThreadAttributeParentProcess, &opt_hParentProcess,
sizeof(opt_hParentProcess), 0, 0);
}
if (ok && opt_lpExplicitHandleList) {
ok = UpdateProcThreadAttribute(
alist, 0, kNtProcThreadAttributeHandleList,
opt_lpExplicitHandleList,
dwExplicitHandleCount * sizeof(*opt_lpExplicitHandleList), 0, 0);
}
// create the process
if (ok) {
struct NtStartupInfoEx info;
bzero(&info, sizeof(info));
info.StartupInfo = *lpStartupInfo;
info.StartupInfo.cb = sizeof(info);
info.lpAttributeList = alist;
if (ok) {
if (CreateProcess(sb->path, sb->cmdline, 0, 0, true,
dwCreationFlags | kNtCreateUnicodeEnvironment |
kNtExtendedStartupinfoPresent |
kNtInheritParentAffinity,
sb->envblock, opt_lpCurrentDirectory,
&info.StartupInfo, opt_out_lpProcessInformation)) {
rc = 0;
} else {
STRACE("CreateProcess() failed w/ %d", GetLastError());
if (GetLastError() == kNtErrorSharingViolation) {
etxtbsy();
}
}
rc = __fix_enotdir(rc, sb->path);
}
} else {
rc = __winerr();
}
// clean up resources
if (alist) {
DeleteProcThreadAttributeList(alist);
}
if (freeme) {
ntspawn_free(freeme);
}
if (dp && dp != GetCurrentProcess()) {
CloseHandle(dp);
}
}
}
if (sb) ntspawn_free(sb);
ALLOW_SIGNALS;
return rc;
}
#endif /* __x86_64__ */

View file

@ -120,11 +120,12 @@ static textwindows int64_t sys_open_nt_impl(int dirfd, const char *path,
// open the file, following symlinks
int e = errno;
int64_t hand = CreateFile(path16, perm | extra_perm, share, 0, disp,
attr | extra_attr, 0);
int64_t hand = CreateFile(path16, perm | extra_perm, share, &kNtIsInheritable,
disp, attr | extra_attr, 0);
if (hand == -1 && errno == EACCES && (flags & O_ACCMODE) == O_RDONLY) {
errno = e;
hand = CreateFile(path16, perm, share, 0, disp, attr | extra_attr, 0);
hand = CreateFile(path16, perm, share, &kNtIsInheritable, disp,
attr | extra_attr, 0);
}
return __fix_enotdir(hand, path16);
@ -137,10 +138,9 @@ static textwindows int sys_open_nt_console(int dirfd,
g_fds.p[fd].kind = kFdConsole;
g_fds.p[fd].flags = flags;
g_fds.p[fd].mode = mode;
g_fds.p[fd].handle = CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite,
kNtFileShareRead, 0, kNtOpenExisting, 0, 0);
g_fds.p[fd].extra = CreateFile(u"CONOUT$", kNtGenericRead | kNtGenericWrite,
kNtFileShareWrite, 0, kNtOpenExisting, 0, 0);
g_fds.p[fd].handle =
CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite, kNtFileShareRead,
&kNtIsInheritable, kNtOpenExisting, 0, 0);
return fd;
}
@ -163,6 +163,7 @@ textwindows int sys_open_nt(int dirfd, const char *file, uint32_t flags,
int fd;
ssize_t rc;
__fds_lock();
if (!(flags & O_CREAT)) mode = 0;
if ((rc = fd = __reservefd_unlocked(-1)) != -1) {
if (!strcmp(file, kNtMagicPaths.devtty)) {
rc = sys_open_nt_console(dirfd, &kNtMagicPaths, flags, mode, fd);

View file

@ -29,7 +29,7 @@
* @param file specifies filesystem path to open
* @return file descriptor, or -1 w/ errno
* @see openat() for further documentation
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe

View file

@ -165,7 +165,7 @@
* @raise ELOOP if `flags` had `O_NOFOLLOW` and `path` is a symbolic link
* @raise ELOOP if a loop was detected resolving components of `path`
* @raise EISDIR if writing is requested and `path` names a directory
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe
@ -178,7 +178,7 @@ int openat(int dirfd, const char *path, int flags, ...) {
va_start(va, flags);
mode = va_arg(va, unsigned);
va_end(va);
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (!path || (IsAsan() && !__asan_is_valid_str(path))) {
rc = efault();
@ -235,7 +235,7 @@ int openat(int dirfd, const char *path, int flags, ...) {
rc = enosys();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("openat(%s, %#s, %s%s) → %d% m", DescribeDirfd(dirfd), path,
DescribeOpenFlags(flags), DescribeOpenMode(flags, mode), rc);
return rc;

View file

@ -95,7 +95,7 @@
* @raise EINVAL if `template` (less the `suffixlen` region) didn't
* end with the string "XXXXXXX"
* @raise EINVAL if `suffixlen` was negative or too large
* @cancellationpoint
* @cancelationpoint
*/
int openatemp(int dirfd, char *template, int suffixlen, int flags, int mode) {
flags &= ~O_ACCMODE;

View file

@ -97,8 +97,8 @@ int openpty(int *mfd, int *sfd, char *name, //
(wsz && !__asan_is_valid(wsz, sizeof(*wsz))))) {
return efault();
}
BLOCK_CANCELLATIONS;
BLOCK_CANCELATION;
rc = openpty_impl(mfd, sfd, name, tio, wsz);
ALLOW_CANCELLATIONS;
ALLOW_CANCELATION;
return rc;
}

View file

View file

@ -1,25 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_OVERLAPPED_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_OVERLAPPED_INTERNAL_H_
#include "libc/nt/struct/overlapped.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define overlapped_cleanup_push(handle, overlap) \
{ \
struct OverlappedCleanup overlapped_cleanup = {handle, overlap}; \
pthread_cleanup_push(overlapped_cleanup_callback, &overlapped_cleanup);
#define overlapped_cleanup_pop() \
pthread_cleanup_pop(false); \
}
struct OverlappedCleanup {
int64_t handle;
struct NtOverlapped *overlap;
};
void overlapped_cleanup_callback(void *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_OVERLAPPED_INTERNAL_H_ */

76
libc/calls/park.c Normal file
View file

@ -0,0 +1,76 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
// each thread has its own pt_futex which is used by both posix signals
// and posix thread cancelation to "park" blocking operations that dont
// need win32 overlapped i/o. the delay is advisory and may be -1 which
// means wait forever. these functions don't guarantee to wait the full
// duration. other threads wanting to deliver a signal, can wake parked
// futexes without releasing them, just to stir up activity. if a futex
// is both woken and released then the cancelation point shall generate
// an eintr. we also abstract checking for signals & thread cancelation
static textwindows int _park_wait(uint32_t msdelay, bool restartable,
struct PosixThread *pt) {
int got, expect = 0;
if (_check_cancel() == -1) return -1;
if (_check_signal(restartable) == -1) return -1;
WaitOnAddress(&pt->pt_futex, &expect, sizeof(expect), msdelay);
got = atomic_load_explicit(&pt->pt_futex, memory_order_acquire);
return got != expect ? eintr() : 0;
}
static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool restartable) {
int rc;
sigset_t om;
struct PosixThread *pt;
pt = _pthread_self();
pt->pt_flags &= ~PT_RESTARTABLE;
if (restartable) pt->pt_flags |= PT_RESTARTABLE;
atomic_store_explicit(&pt->pt_futex, 0, memory_order_release);
atomic_store_explicit(&pt->pt_blocker, &pt->pt_futex, memory_order_release);
om = __sig_beginwait(waitmask);
rc = _park_wait(msdelay, restartable, pt);
if (rc == -1 && errno == EINTR) _check_cancel();
__sig_finishwait(om);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release);
pt->pt_flags &= ~PT_RESTARTABLE;
return rc;
}
textwindows int _park_norestart(uint32_t msdelay, sigset_t waitmask) {
return _park_thread(msdelay, waitmask, false);
}
textwindows int _park_restartable(uint32_t msdelay, sigset_t waitmask) {
return _park_thread(msdelay, waitmask, true);
}
#endif /* __x86_64__ */

View file

@ -19,13 +19,12 @@
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#ifdef __x86_64__
textwindows int sys_pause_nt(void) {
int rc;
while (!(rc = _check_interrupts(0))) {
if ((rc = __pause_thread(__SIG_SIG_INTERVAL_MS))) {
break;
}
}
while (!(rc = _park_norestart(-1u, 0))) donothing;
return rc;
}
#endif /* __x86_64__ */

View file

@ -40,14 +40,14 @@
* @return -1 w/ errno
* @raise ECANCELED if this thread was canceled in masked mode
* @raise EINTR if interrupted by a signal
* @cancellationpoint
* @cancelationpoint
* @see sigsuspend()
* @norestart
*/
int pause(void) {
int rc;
STRACE("pause() → [...]");
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (!IsWindows()) {
// We'll polyfill pause() using select() with a null timeout, which
@ -72,7 +72,7 @@ int pause(void) {
rc = sys_pause_nt();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("[...] pause → %d% m", rc);
return rc;
}

View file

@ -17,9 +17,10 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/handlock.internal.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/creationdisposition.h"
@ -30,7 +31,7 @@
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) {
static textwindows int sys_pipe_nt_impl(int pipefd[2], unsigned flags) {
uint32_t mode;
int64_t hin, hout;
int reader, writer;
@ -53,11 +54,11 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) {
}
__fds_unlock();
hin = CreateNamedPipe(pipename, kNtPipeAccessInbound | kNtFileFlagOverlapped,
mode, 1, PIPE_BUF, PIPE_BUF, 0, 0);
mode, 1, PIPE_BUF, PIPE_BUF, 0, &kNtIsInheritable);
__fds_lock();
if (hin != -1) {
if ((hout = CreateFile(pipename, kNtGenericWrite, 0, 0, kNtOpenExisting,
kNtFileFlagOverlapped, 0)) != -1) {
if ((hout = CreateFile(pipename, kNtGenericWrite, 0, &kNtIsInheritable,
kNtOpenExisting, kNtFileFlagOverlapped, 0)) != -1) {
g_fds.p[reader].kind = kFdFile;
g_fds.p[reader].flags = O_RDONLY | flags;
g_fds.p[reader].mode = 0010444;
@ -79,3 +80,11 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) {
__fds_unlock();
return -1;
}
textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) {
int rc;
BLOCK_SIGNALS;
rc = sys_pipe_nt_impl(pipefd, flags);
ALLOW_SIGNALS;
return rc;
}

View file

@ -22,6 +22,7 @@
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/dce.h"
#include "libc/errno.h"
@ -41,16 +42,17 @@
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/pollfd.internal.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
// Polls on the New Technology.
@ -61,25 +63,22 @@
textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
const sigset_t *sigmask) {
bool ok;
uint64_t m;
bool interrupted;
sigset_t oldmask;
uint64_t millis;
uint32_t cm, avail, waitfor;
struct sys_pollfd_nt pipefds[8];
struct sys_pollfd_nt sockfds[64];
int pipeindices[ARRAYLEN(pipefds)];
int sockindices[ARRAYLEN(sockfds)];
struct timespec started, deadline, remain, now;
int i, rc, sn, pn, gotinvals, gotpipes, gotsocks;
#if IsModeDbg()
struct timespec noearlier =
timespec_add(timespec_real(), timespec_frommillis(ms ? *ms : -1u));
#endif
BLOCK_SIGNALS;
started = timespec_real();
deadline = timespec_add(started, timespec_frommillis(ms ? *ms : -1u));
// do the planning
// we need to read static variables
// we might need to spawn threads and open pipes
m = atomic_exchange(&__get_tls()->tib_sigmask, -1);
__fds_lock();
for (gotinvals = rc = sn = pn = i = 0; i < nfds; ++i) {
if (fds[i].fd < 0) continue;
@ -114,7 +113,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
pipefds[pn].events = fds[i].events & (POLLIN | POLLOUT);
break;
default:
__builtin_unreachable();
break;
}
++pn;
} else {
@ -127,7 +126,6 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
}
}
__fds_unlock();
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
if (rc) {
// failed to create a polling solution
goto Finished;
@ -155,8 +153,8 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
pipefds[i].revents |= POLLERR;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
if (CountConsoleInputBytes(g_fds.p + fds[pipeindices[i]].fd)) {
pipefds[i].revents |= POLLIN;
if (CountConsoleInputBytes()) {
pipefds[i].revents |= POLLIN; // both >0 and -1 (eof) are pollin
}
} else {
// we have no way of polling if a non-socket is readable yet
@ -172,31 +170,36 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
// compute a small time slice we don't mind sleeping for
if (sn) {
if ((gotsocks = WSAPoll(sockfds, sn, 0)) == -1) {
return __winsockerr();
rc = __winsockerr();
goto Finished;
}
} else {
gotsocks = 0;
}
waitfor = MIN(__SIG_POLL_INTERVAL_MS, *ms);
if (!gotinvals && !gotsocks && !gotpipes && waitfor) {
POLLTRACE("poll() sleeping for %'d out of %'u ms", waitfor, *ms);
struct PosixThread *pt = _pthread_self();
pt->abort_errno = 0;
if (sigmask) __sig_mask(SIG_SETMASK, sigmask, &oldmask);
interrupted = _check_interrupts(0) || __pause_thread(waitfor);
if (sigmask) __sig_mask(SIG_SETMASK, &oldmask, 0);
if (interrupted) return -1;
if (*ms != -1u) {
if (waitfor < *ms) {
*ms -= waitfor;
} else {
*ms = 0;
// add some artificial delay, which we use as an opportunity to also
// check for pending signals, thread cancelation, etc.
waitfor = 0;
if (!gotinvals && !gotsocks && !gotpipes) {
now = timespec_real();
if (timespec_cmp(now, deadline) < 0) {
remain = timespec_sub(deadline, now);
millis = timespec_tomillis(remain);
waitfor = MIN(millis, 0xffffffffu);
waitfor = MIN(waitfor, __SIG_POLL_INTERVAL_MS);
if (waitfor) {
POLLTRACE("poll() sleeping for %'d out of %'lu ms", waitfor,
timespec_tomillis(remain));
if ((rc = _park_norestart(waitfor, sigmask ? *sigmask : 0)) == -1) {
goto Finished; // eintr, ecanceled, etc.
}
}
}
}
// we gave all the sockets and all the named pipes a shot
// if we found anything at all then it's time to end work
if (gotinvals || gotpipes || gotsocks || !*ms) {
if (gotinvals || gotpipes || gotsocks || !waitfor) {
break;
}
}
@ -221,15 +224,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
rc = gotinvals + gotpipes + gotsocks;
Finished:
#if IsModeDbg()
struct timespec ended = timespec_real();
if (!rc && timespec_cmp(ended, noearlier) < 0) {
STRACE("poll() ended %'ld ns too soon!",
timespec_tonanos(timespec_sub(noearlier, ended)));
}
#endif
ALLOW_SIGNALS;
return rc;
}

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/bo.internal.h"
#include "libc/calls/cp.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
@ -60,14 +59,14 @@
* was determined about the file descriptor
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/
int poll(struct pollfd *fds, size_t nfds, int timeout_ms) {
int rc;
size_t n;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (IsAsan() &&
(ckd_mul(&n, nfds, sizeof(struct pollfd)) || !__asan_is_valid(fds, n))) {
@ -79,13 +78,11 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) {
rc = sys_poll_metal(fds, nfds, timeout_ms);
}
} else {
BEGIN_BLOCKING_OPERATION;
uint32_t ms = timeout_ms >= 0 ? timeout_ms : -1u;
rc = sys_poll_nt(fds, nfds, &ms, 0);
END_BLOCKING_OPERATION;
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("poll(%s, %'zu, %'d) → %d% lm", DescribePollFds(rc, fds, nfds), nfds,
timeout_ms, rc);
return rc;

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/bo.internal.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
@ -55,7 +54,7 @@
* @param sigmask may be null in which case no mask change happens
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/
@ -65,7 +64,7 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
int e, rc;
sigset_t oldmask;
struct timespec ts, *tsp;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (IsAsan() &&
(ckd_mul(&n, nfds, sizeof(struct pollfd)) || !__asan_is_valid(fds, n) ||
@ -98,12 +97,10 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) {
ms = -1u;
}
BEGIN_BLOCKING_OPERATION;
rc = sys_poll_nt(fds, nfds, &ms, sigmask);
END_BLOCKING_OPERATION;
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("ppoll(%s, %'zu, %s, %s) → %d% lm", DescribePollFds(rc, fds, nfds),
nfds, DescribeTimespec(0, timeout), DescribeSigset(0, sigmask), rc);
return rc;

View file

@ -50,13 +50,13 @@
* @raise EINTR if signal was delivered instead
* @raise ECANCELED if thread was cancelled in masked mode
* @see pwrite(), write()
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @vforksafe
*/
ssize_t pread(int fd, void *buf, size_t size, int64_t offset) {
ssize_t rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (offset < 0) {
rc = einval();
@ -79,7 +79,7 @@ ssize_t pread(int fd, void *buf, size_t size, int64_t offset) {
}
npassert(rc == -1 || (size_t)rc <= size);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
DATATRACE("pread(%d, [%#.*hhs%s], %'zu, %'zd) → %'zd% m", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, offset, rc);
return rc;

View file

@ -112,15 +112,15 @@ static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) {
* Reads with maximum generality.
*
* @return number of bytes actually read, or -1 w/ errno
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @vforksafe
*/
ssize_t preadv(int fd, struct iovec *iov, int iovlen, int64_t off) {
ssize_t rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
rc = Preadv(fd, iov, iovlen, off);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("preadv(%d, [%s], %d, %'ld) → %'ld% m", fd,
DescribeIovec(rc, iov, iovlen), iovlen, off, rc);
return rc;

View file

@ -19,6 +19,7 @@
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
static const char *__fdkind2str(int x) {
@ -29,8 +30,6 @@ static const char *__fdkind2str(int x) {
return "kFdFile";
case kFdSocket:
return "kFdSocket";
case kFdProcess:
return "kFdProcess";
case kFdConsole:
return "kFdConsole";
case kFdSerial:
@ -44,17 +43,17 @@ static const char *__fdkind2str(int x) {
}
}
void __printfds(void) {
void __printfds(struct Fd *fds, size_t fdslen) {
int i;
__fds_lock();
for (i = 0; i < g_fds.n; ++i) {
if (!g_fds.p[i].kind) continue;
kprintf("%3d %s", i, __fdkind2str(g_fds.p[i].kind));
if (g_fds.p[i].flags) kprintf(" flags=%#x", g_fds.p[i].flags);
if (g_fds.p[i].mode) kprintf(" mode=%#o", g_fds.p[i].mode);
if (g_fds.p[i].handle) kprintf(" handle=%ld", g_fds.p[i].handle);
if (g_fds.p[i].extra) kprintf(" extra=%ld", g_fds.p[i].extra);
char buf[128];
for (i = 0; i < fdslen; ++i) {
if (!fds[i].kind) continue;
kprintf("%3d %s", i, __fdkind2str(fds[i].kind));
if (fds[i].flags) {
kprintf(" flags=%s", (DescribeOpenFlags)(buf, fds[i].flags));
}
if (fds[i].mode) kprintf(" mode=%#o", fds[i].mode);
if (fds[i].handle) kprintf(" handle=%ld", fds[i].handle);
kprintf("\n");
}
__fds_unlock();
}

View file

@ -44,14 +44,14 @@
* @return [1..size] bytes on success, or -1 w/ errno; noting zero is
* impossible unless size was passed as zero to do an error check
* @see pread(), write()
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @vforksafe
*/
ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) {
ssize_t rc;
size_t wrote;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (offset < 0) {
rc = einval();
@ -79,7 +79,7 @@ ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) {
}
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
DATATRACE("pwrite(%d, %#.*hhs%s, %'zu, %'zd) → %'zd% m", fd,
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, offset, rc);
return rc;

View file

@ -116,15 +116,15 @@ static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen,
* call using pwrite().
*
* @return number of bytes actually sent, or -1 w/ errno
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @vforksafe
*/
ssize_t pwritev(int fd, const struct iovec *iov, int iovlen, int64_t off) {
ssize_t rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
rc = Pwritev(fd, iov, iovlen, off);
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("pwritev(%d, %s, %d, %'ld) → %'ld% m", fd,
DescribeIovec(rc != -1 ? rc : -2, iov, iovlen), iovlen, off, rc);
return rc;

View file

@ -23,7 +23,6 @@
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/thread/tls.h"
/**
* Sends signal to self.

View file

@ -16,12 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/atomic.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/ttydefaults.h"
#include "libc/cosmo.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
@ -34,41 +37,55 @@
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/filesharemode.h"
#include "libc/nt/enum/vk.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/inputrecord.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/str/str.h"
#include "libc/str/utf16.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
static const struct {
/**
* @fileoverview Cosmopolitan Standard Input
*
* This file implements pollable terminal i/o for Windows consoles. On
* Windows 10 the "virtual terminal processing" feature works great on
* output but their solution for input processing isn't good enough to
* support running Linux programs like Emacs. This polyfill fixes that
* and it most importantly ensures we can poll() standard input, which
* would otherwise have been impossible. We aren't using threads. What
* we do instead is have termios behaviors e.g. canonical mode editing
* happen on demand as a side effect of read/poll/ioctl activity.
*/
struct VirtualKey {
int vk;
int normal_str;
int shift_str;
int ctrl_str;
int shift_ctrl_str;
} kVirtualKey[] = {
};
#define S(s) W(s "\0\0")
#define W(s) (s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
static const struct VirtualKey kVirtualKey[] = {
{kNtVkUp, S("A"), S("1;2A"), S("1;5A"), S("1;6A")},
{kNtVkDown, S("B"), S("1;2B"), S("1;5B"), S("1;6B")},
{kNtVkLeft, S("D"), S("1;2D"), S("1;5D"), S("1;6D")},
{kNtVkRight, S("C"), S("1;2C"), S("1;5C"), S("1;6C")},
{kNtVkLeft, S("D"), S("1;2D"), S("1;5D"), S("1;6D")},
{kNtVkInsert, S("2~"), S("2;2~"), S("2;5~"), S("2;6~")},
{kNtVkDelete, S("3~"), S("3;2~"), S("3;5~"), S("3;6~")},
{kNtVkHome, S("H"), S("1;2H"), S("1;5H"), S("1;6H")},
@ -87,8 +104,18 @@ static const struct {
{kNtVkF10, S("21~"), S("34~"), S("21^"), S("34^")},
{kNtVkF11, S("23~"), S("23$"), S("23^"), S("23@")},
{kNtVkF12, S("24~"), S("24$"), S("24^"), S("24@")},
#undef W
#undef S
{0},
};
// TODO: How can we configure `less` to not need this bloat?
static const struct VirtualKey kDecckm[] = {
{kNtVkUp, -S("OA"), -S("OA"), S("A"), S("A")},
{kNtVkDown, -S("OB"), -S("OB"), S("B"), S("B")},
{kNtVkRight, -S("OC"), -S("OC"), S("C"), S("C")},
{kNtVkLeft, -S("OD"), -S("OD"), S("D"), S("D")},
{kNtVkPrior, S("5~"), S("5;2~"), S("5;5~"), S("5;6~")},
{kNtVkNext, S("6~"), S("6;2~"), S("6;5~"), S("6;6~")},
{0},
};
#define KEYSTROKE_CONTAINER(e) DLL_CONTAINER(struct Keystroke, elem, e)
@ -100,6 +127,8 @@ struct Keystroke {
};
struct Keystrokes {
atomic_uint once;
int64_t cin, cot;
struct Dll *list;
struct Dll *line;
struct Dll *free;
@ -108,10 +137,27 @@ struct Keystrokes {
uint16_t utf16hs;
pthread_mutex_t lock;
struct Keystroke pool[512];
const struct VirtualKey *vkt;
};
static struct Keystrokes __keystroke;
textwindows void __keystroke_wipe(void) {
bzero(&__keystroke, sizeof(__keystroke));
}
static textwindows void OpenConsole(void) {
__keystroke.vkt = kVirtualKey;
__keystroke.cin = CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite,
kNtFileShareRead, 0, kNtOpenExisting, 0, 0);
__keystroke.cot = CreateFile(u"CONOUT$", kNtGenericRead | kNtGenericWrite,
kNtFileShareWrite, 0, kNtOpenExisting, 0, 0);
}
static textwindows void InitConsole(void) {
cosmo_once(&__keystroke.once, OpenConsole);
}
static textwindows void LockKeystrokes(void) {
pthread_mutex_lock(&__keystroke.lock);
}
@ -120,30 +166,34 @@ static textwindows void UnlockKeystrokes(void) {
pthread_mutex_unlock(&__keystroke.lock);
}
static textwindows uint64_t BlockSignals(void) {
return atomic_exchange(&__get_tls()->tib_sigmask, -1);
textwindows int64_t GetConsoleInputHandle(void) {
InitConsole();
return __keystroke.cin;
}
static textwindows void UnblockSignals(uint64_t mask) {
atomic_store_explicit(&__get_tls()->tib_sigmask, mask, memory_order_release);
textwindows int64_t GetConsoleOutputHandle(void) {
InitConsole();
return __keystroke.cot;
}
static textwindows int RaiseSignal(int sig) {
__get_tls()->tib_sigpending |= 1ull << (sig - 1);
return 0;
static textwindows bool IsMouseModeCommand(int x) {
return x == 1000 || // SET_VT200_MOUSE
x == 1002 || // SET_BTN_EVENT_MOUSE
x == 1006 || // SET_SGR_EXT_MODE_MOUSE
x == 1015; // SET_URXVT_EXT_MODE_MOUSE
}
static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
for (int i = 0; i < ARRAYLEN(kVirtualKey); ++i) {
if (kVirtualKey[i].vk == vk) {
for (int i = 0; __keystroke.vkt[i].vk; ++i) {
if (__keystroke.vkt[i].vk == vk) {
if (shift && ctrl) {
return kVirtualKey[i].shift_ctrl_str;
return __keystroke.vkt[i].shift_ctrl_str;
} else if (shift) {
return kVirtualKey[i].shift_str;
return __keystroke.vkt[i].shift_str;
} else if (ctrl) {
return kVirtualKey[i].ctrl_str;
return __keystroke.vkt[i].ctrl_str;
} else {
return kVirtualKey[i].normal_str;
return __keystroke.vkt[i].normal_str;
}
}
}
@ -152,7 +202,7 @@ static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
uint16_t c = r->Event.KeyEvent.uChar.UnicodeChar;
uint32_t c = r->Event.KeyEvent.uChar.UnicodeChar;
uint16_t vk = r->Event.KeyEvent.wVirtualKeyCode;
uint16_t cks = r->Event.KeyEvent.dwControlKeyState;
@ -162,6 +212,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
}
#if 0
// this code is useful for troubleshooting why keys don't work
kprintf("bKeyDown=%hhhd wVirtualKeyCode=%s wVirtualScanCode=%s "
"UnicodeChar=%#x[%#lc] dwControlKeyState=%s\n",
r->Event.KeyEvent.bKeyDown,
@ -172,7 +223,7 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
DescribeControlKeyState(r->Event.KeyEvent.dwControlKeyState));
#endif
// process arrow keys, function keys, etc.
// turn arrow/function keys into vt100/ansi/xterm byte sequences
int n = 0;
int v = GetVirtualKey(vk, !!(cks & kNtShiftPressed),
!!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)));
@ -191,31 +242,28 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
return n;
}
// ^/ should be interpreted as ^_
// ^/ (crtl+slash) maps to ^_ (ctrl-hyphen) on linux
if (vk == kNtVkOem_2 && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
p[n++] = 037;
return n;
}
// everything needs a unicode mapping from here on out
// handle some stuff microsoft doesn't encode, e.g. ctrl+alt+b
// handle cases where win32 doesn't provide character
if (!c) {
if (isgraph(vk) && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = CTRL(vk);
if (vk == '2' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = 0; // ctrl-2 → "\000"
} else if (isascii(vk) && isdigit(vk) &&
(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = 030 + (vk - '0'); // e.g. ctrl-3 → "\033"
} else if (isascii(vk) && isgraph(vk) &&
(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = vk ^ 0100; // e.g. ctrl-alt-b → "\033\002"
} else {
return 0;
}
}
// shift-tab is backtab or ^[Z
if (vk == kNtVkTab && (cks & (kNtShiftPressed))) {
p[n++] = 033;
p[n++] = '[';
p[n++] = 'Z';
return n;
}
// translate utf-16 into utf-32
// convert utf-16 to utf-32
if (IsHighSurrogate(c)) {
__keystroke.utf16hs = c;
return 0;
@ -224,29 +272,30 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
c = MergeUtf16(__keystroke.utf16hs, c);
}
// enter sends \r in a raw terminals
// enter sends \r with raw terminals
// make it a multics newline instead
if (c == '\r' && !(__ttyconf.magic & kTtyNoCr2Nl)) {
c = '\n';
}
// microsoft doesn't encode ctrl-space (^@) as nul
// detecting it is also impossible w/ kNtEnableVirtualTerminalInput
// ctrl-space (^@) is literally zero
if (c == ' ' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = '\0';
}
// make it possible to distinguish ctrl-h (^H) from backspace (^?)
// make backspace (^?) distinguishable from ctrl-h (^H)
if (c == kNtVkBack && !(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
c = 0177;
}
// handle ctrl-c and ctrl-\, which tcsetattr() is able to remap
// handle ctrl-\ and ctrl-c
// note we define _POSIX_VDISABLE as zero
// tcsetattr() lets anyone reconfigure these keybindings
if (c && !(__ttyconf.magic & kTtyNoIsigs)) {
if (c == __ttyconf.vintr) {
return RaiseSignal(SIGINT);
return __sig_enqueue(SIGINT);
} else if (c == __ttyconf.vquit) {
return RaiseSignal(SIGQUIT);
return __sig_enqueue(SIGQUIT);
}
}
@ -263,12 +312,14 @@ static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
}
// insert esc prefix when alt is held
// for example "h" becomes "\033h" (alt-h)
// if up arrow is "\033[A" then alt-up is "\033\033[A"
if ((cks & (kNtLeftAltPressed | kNtRightAltPressed)) &&
r->Event.KeyEvent.bKeyDown) {
p[n++] = 033;
}
// convert utf-32 to utf-8
// finally apply thompson-pike varint encoding
uint64_t w = tpenc(c);
do p[n++] = w;
while ((w >>= 8));
@ -343,7 +394,7 @@ static textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r,
case kNtMouseEvent:
return ProcessMouseEvent(r, p);
case kNtWindowBufferSizeEvent:
return RaiseSignal(SIGWINCH);
return __sig_enqueue(SIGWINCH);
default:
return 0;
}
@ -377,60 +428,51 @@ static textwindows struct Keystroke *NewKeystroke(void) {
return k;
}
static textwindows void WriteTty(struct Fd *f, const char *p, size_t n) {
int64_t hOutput;
uint32_t dwConsoleMode;
if (f->kind == kFdConsole) {
hOutput = f->extra;
} else if (g_fds.p[1].kind == kFdFile &&
GetConsoleMode(g_fds.p[1].handle, &dwConsoleMode)) {
hOutput = g_fds.p[1].handle;
} else if (g_fds.p[2].kind == kFdFile &&
GetConsoleMode(g_fds.p[2].handle, &dwConsoleMode)) {
hOutput = g_fds.p[2].handle;
} else {
hOutput = g_fds.p[1].handle;
}
WriteFile(hOutput, p, n, 0, 0);
static textwindows void WriteTty(const char *p, size_t n) {
WriteFile(__keystroke.cot, p, n, 0, 0);
}
static textwindows bool IsCtl(int c) {
return isascii(c) && iscntrl(c) && c != '\n' && c != '\t';
}
static textwindows void WriteTtyCtl(struct Fd *f, const char *p, size_t n) {
static textwindows void WriteCtl(const char *p, size_t n) {
size_t i;
for (i = 0; i < n; ++i) {
if (IsCtl(p[i])) {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteTty(f, ctl, 2);
WriteTty(ctl, 2);
} else {
WriteTty(f, p + i, 1);
WriteTty(p + i, 1);
}
}
}
static textwindows void EchoTty(struct Fd *f, const char *p, size_t n) {
static textwindows void EchoTty(const char *p, size_t n) {
if (__ttyconf.magic & kTtyEchoRaw) {
WriteTty(f, p, n);
WriteTty(p, n);
} else {
WriteTtyCtl(f, p, n);
WriteCtl(p, n);
}
}
static textwindows bool EraseKeystroke(struct Fd *f) {
static textwindows void EraseCharacter(void) {
WriteTty("\b \b", 3);
}
static textwindows bool EraseKeystroke(void) {
struct Dll *e;
if ((e = dll_last(__keystroke.line))) {
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
dll_remove(&__keystroke.line, e);
dll_make_first(&__keystroke.free, e);
for (int i = k->buflen; i--;) {
if ((k->buf[i] & 0300) == 0200) continue;
WriteTty(f, "\b \b", 3);
if ((k->buf[i] & 0300) == 0200) continue; // utf-8 cont
EraseCharacter();
if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i])) {
WriteTty(f, "\b \b", 3);
EraseCharacter();
}
}
return true;
@ -439,8 +481,7 @@ static textwindows bool EraseKeystroke(struct Fd *f) {
}
}
static textwindows void IngestConsoleInputRecord(struct Fd *f,
struct NtInputRecord *r) {
static textwindows void IngestConsoleInputRecord(struct NtInputRecord *r) {
// convert win32 console event into ansi
int len;
@ -453,7 +494,7 @@ static textwindows void IngestConsoleInputRecord(struct Fd *f,
if (len == 1 && buf[0] && //
(buf[0] & 255) == __ttyconf.verase && //
!(__ttyconf.magic & kTtyUncanon)) {
EraseKeystroke(f);
EraseKeystroke();
return;
}
@ -461,15 +502,15 @@ static textwindows void IngestConsoleInputRecord(struct Fd *f,
if (len == 1 && buf[0] && //
(buf[0] & 255) == __ttyconf.vkill && //
!(__ttyconf.magic & kTtyUncanon)) {
while (EraseKeystroke(f)) {
while (EraseKeystroke()) {
}
return;
}
// allocate an object to hold this keystroke
// allocate object to hold keystroke
struct Keystroke *k;
if (!(k = NewKeystroke())) {
STRACE("ran out of memory to hold keystroke %#.*s", len, buf);
STRACE("out of keystroke memory");
return;
}
memcpy(k->buf, buf, sizeof(k->buf));
@ -478,7 +519,7 @@ static textwindows void IngestConsoleInputRecord(struct Fd *f,
// echo input if it was successfully recorded
// assuming the win32 console isn't doing it already
if (!(__ttyconf.magic & kTtySilence)) {
EchoTty(f, buf, len);
EchoTty(buf, len);
}
// save keystroke to appropriate list
@ -487,70 +528,64 @@ static textwindows void IngestConsoleInputRecord(struct Fd *f,
} else {
dll_make_last(&__keystroke.line, &k->elem);
// handle end-of-line in canonical mode
// handle enter in canonical mode
if (len == 1 && buf[0] &&
((buf[0] & 255) == '\n' || //
(buf[0] & 255) == __ttyconf.veol || //
(buf[0] & 255) == __ttyconf.veol2)) {
dll_make_last(&__keystroke.list, __keystroke.line);
__keystroke.line = 0;
return;
}
}
}
static textwindows void IngestConsoleInput(struct Fd *f) {
static textwindows void IngestConsoleInput(void) {
uint32_t i, n;
struct NtInputRecord records[16];
if (!__keystroke.end_of_file) {
do {
if (GetNumberOfConsoleInputEvents(f->handle, &n)) {
if (n) {
n = MIN(ARRAYLEN(records), n);
if (ReadConsoleInput(f->handle, records, n, &n)) {
for (i = 0; i < n && !__keystroke.end_of_file; ++i) {
IngestConsoleInputRecord(f, records + i);
}
} else {
STRACE("ReadConsoleInput failed w/ %d", GetLastError());
__keystroke.end_of_file = true;
break;
}
}
} else {
STRACE("GetNumberOfConsoleInputRecords failed w/ %d", GetLastError());
__keystroke.end_of_file = true;
break;
}
} while (n == ARRAYLEN(records));
for (;;) {
if (__keystroke.end_of_file) return;
if (!GetNumberOfConsoleInputEvents(__keystroke.cin, &n)) {
goto UnexpectedEof;
}
if (!n) return;
n = MIN(ARRAYLEN(records), n);
if (!ReadConsoleInput(__keystroke.cin, records, n, &n)) {
goto UnexpectedEof;
}
for (i = 0; i < n && !__keystroke.end_of_file; ++i) {
IngestConsoleInputRecord(records + i);
}
}
UnexpectedEof:
STRACE("console read error %d", GetLastError());
__keystroke.end_of_file = true;
}
textwindows int FlushConsoleInputBytes(int64_t handle) {
// Discards all unread stdin bytes.
textwindows int FlushConsoleInputBytes(void) {
int rc;
uint64_t m;
m = BlockSignals();
BLOCK_SIGNALS;
InitConsole();
LockKeystrokes();
if (FlushConsoleInputBuffer(handle)) {
dll_make_first(&__keystroke.free, __keystroke.list);
__keystroke.list = 0;
dll_make_first(&__keystroke.free, __keystroke.line);
__keystroke.line = 0;
rc = 0;
} else {
rc = __winerr();
}
FlushConsoleInputBuffer(__keystroke.cin);
dll_make_first(&__keystroke.free, __keystroke.list);
__keystroke.list = 0;
dll_make_first(&__keystroke.free, __keystroke.line);
__keystroke.line = 0;
rc = 0;
UnlockKeystrokes();
UnblockSignals(m);
ALLOW_SIGNALS;
return rc;
}
textwindows int CountConsoleInputBytes(struct Fd *f) {
int count = 0;
// Returns number of stdin bytes that may be read without blocking.
textwindows int CountConsoleInputBytes(void) {
struct Dll *e;
uint64_t m = BlockSignals();
int count = 0;
BLOCK_SIGNALS;
InitConsole();
LockKeystrokes();
IngestConsoleInput(f);
IngestConsoleInput();
for (e = dll_first(__keystroke.list); e; e = dll_next(__keystroke.list, e)) {
count += KEYSTROKE_CONTAINER(e)->buflen;
}
@ -558,10 +593,75 @@ textwindows int CountConsoleInputBytes(struct Fd *f) {
count = -1;
}
UnlockKeystrokes();
UnblockSignals(m);
ALLOW_SIGNALS;
return count;
}
// Intercept ANSI TTY commands that enable features.
textwindows void InterceptTerminalCommands(const char *data, size_t size) {
int i;
unsigned x;
bool ismouse;
uint32_t cm, cm2;
enum { ASC, ESC, CSI, CMD } t;
GetConsoleMode(GetConsoleInputHandle(), &cm), cm2 = cm;
for (ismouse = false, x = i = t = 0; i < size; ++i) {
switch (t) {
case ASC:
if (data[i] == 033) {
t = ESC;
}
break;
case ESC:
if (data[i] == '[') {
t = CSI;
} else {
t = ASC;
}
break;
case CSI:
if (data[i] == '?') {
t = CMD;
x = 0;
} else {
t = ASC;
}
break;
case CMD:
if ('0' <= data[i] && data[i] <= '9') {
x *= 10;
x += data[i] - '0';
} else if (data[i] == ';') {
ismouse |= IsMouseModeCommand(x);
x = 0;
} else if (data[i] == 'h') {
if (x == 1) {
__keystroke.vkt = kDecckm; // \e[?1h decckm on
} else if ((ismouse |= IsMouseModeCommand(x))) {
__ttyconf.magic |= kTtyXtMouse;
cm2 |= kNtEnableMouseInput;
cm2 &= ~kNtEnableQuickEditMode; // take mouse
}
t = ASC;
} else if (data[i] == 'l') {
if (x == 1) {
__keystroke.vkt = kVirtualKey; // \e[?1l decckm off
} else if ((ismouse |= IsMouseModeCommand(x))) {
__ttyconf.magic &= ~kTtyXtMouse;
cm2 |= kNtEnableQuickEditMode; // release mouse
}
t = ASC;
}
break;
default:
__builtin_unreachable();
}
}
if (cm2 != cm) {
SetConsoleMode(GetConsoleInputHandle(), cm2);
}
}
static textwindows bool DigestConsoleInput(char *data, size_t size, int *rc) {
// handle eof once available input is consumed
@ -604,137 +704,69 @@ static textwindows bool DigestConsoleInput(char *data, size_t size, int *rc) {
}
}
static textwindows ssize_t ReadFromWindowsConsole(struct Fd *f, void *data,
size_t size) {
int rc = -1;
for (;;) {
bool done = false;
uint64_t m;
m = BlockSignals();
LockKeystrokes();
IngestConsoleInput(f);
done = DigestConsoleInput(data, size, &rc);
UnlockKeystrokes();
UnblockSignals(m);
if (done) break;
if (f->flags & O_NONBLOCK) return eagain();
uint32_t ms = __SIG_POLL_INTERVAL_MS;
if (!__ttyconf.vmin) {
if (!__ttyconf.vtime) {
return 0;
} else {
ms = __ttyconf.vtime * 100;
}
}
if (_check_interrupts(kSigOpRestartable)) return -1;
if (__pause_thread(ms)) {
if (errno == EAGAIN) {
errno = EINTR; // TODO(jart): Why do we need it?
}
return -1;
static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) {
int rc;
sigset_t m;
int64_t sem;
uint32_t ms = -1u;
if (!__ttyconf.vmin) {
if (!__ttyconf.vtime) {
return 0; // non-blocking w/o raising eagain
} else {
ms = __ttyconf.vtime * 100;
}
}
if (f->flags & O_NONBLOCK) {
return eagain(); // standard unix non-blocking
}
struct PosixThread *pt = _pthread_self();
pt->pt_flags |= PT_RESTARTABLE;
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
pthread_cleanup_push((void *)CloseHandle, (void *)sem);
atomic_store_explicit(&pt->pt_futex, 0, memory_order_release);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release);
m = __sig_beginwait(waitmask);
if ((rc = _check_cancel()) != -1 && (rc = _check_signal(true)) != -1) {
WaitForMultipleObjects(2, (int64_t[2]){sem, __keystroke.cin}, 0, ms);
if (~pt->pt_flags & PT_RESTARTABLE) rc = eintr();
if (rc == -1 && errno == EINTR) _check_cancel();
}
__sig_finishwait(m);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release);
pthread_cleanup_pop(true);
pt->pt_flags &= ~PT_RESTARTABLE;
return rc;
}
static textwindows ssize_t ReadFromConsole(struct Fd *f, void *data,
size_t size, sigset_t waitmask) {
int rc;
bool done = false;
InitConsole();
do {
LockKeystrokes();
IngestConsoleInput();
done = DigestConsoleInput(data, size, &rc);
UnlockKeystrokes();
} while (!done && !(rc = WaitForConsole(f, waitmask)));
return rc;
}
textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
int64_t offset) {
int64_t offset, sigset_t waitmask) {
bool32 ok;
struct Fd *f;
uint32_t got;
int64_t handle;
struct PosixThread *pt;
f = g_fds.p + fd;
handle = f->handle;
pt = _pthread_self();
pt->abort_errno = EAGAIN;
size = MIN(size, 0x7ffff000);
bool pwriting = offset != -1;
bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
bool nonblock = !!(f->flags & O_NONBLOCK);
if (pwriting && !seekable) {
return espipe();
}
if (!pwriting) {
offset = 0;
// switch to terminal polyfill if reading from win32 console
struct Fd *f = g_fds.p + fd;
if (f->kind == kFdConsole) {
return ReadFromConsole(f, data, size, waitmask);
}
uint32_t cm;
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
return ReadFromWindowsConsole(f, data, size);
}
if (!pwriting && seekable) {
pthread_mutex_lock(&f->lock);
offset = f->pointer;
}
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
.Pointer = offset};
// the win32 manual says it's important to *not* put &got here
// since for overlapped i/o, we always use GetOverlappedResult
ok = ReadFile(handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
BlockingOperation:
if (!nonblock) {
pt->ioverlap = &overlap;
pt->iohandle = handle;
}
if (nonblock) {
CancelIoEx(handle, &overlap);
} else if (_check_interrupts(kSigOpRestartable)) {
Interrupted:
pt->abort_errno = errno;
CancelIoEx(handle, &overlap);
} else {
for (;;) {
uint32_t i;
if (g_fds.stdin.inisem) {
ReleaseSemaphore(g_fds.stdin.inisem, 1, 0);
}
i = WaitForSingleObject(overlap.hEvent, __SIG_IO_INTERVAL_MS);
if (i == kNtWaitTimeout) {
if (_check_interrupts(kSigOpRestartable)) {
goto Interrupted;
}
} else {
break;
}
}
}
pt->ioverlap = 0;
pt->iohandle = 0;
ok = true;
}
if (ok) {
// overlapped is allocated on stack, so it's important we wait
// for windows to acknowledge that it's done using that memory
ok = GetOverlappedResult(handle, &overlap, &got, nonblock);
if (!ok && GetLastError() == kNtErrorIoIncomplete) {
goto BlockingOperation;
}
}
CloseHandle(overlap.hEvent);
if (!pwriting && seekable) {
if (ok) f->pointer = offset + got;
pthread_mutex_unlock(&f->lock);
}
if (ok) {
return got;
}
errno_t err;
if (_weaken(pthread_testcancel_np) &&
(err = _weaken(pthread_testcancel_np)())) {
return ecanceled();
}
// perform heavy lifting
ssize_t rc;
rc = sys_readwrite_nt(fd, data, size, offset, f->handle, waitmask, ReadFile);
if (rc != -2) return rc;
// mops up win32 errors
switch (GetLastError()) {
case kNtErrorBrokenPipe: // broken pipe
case kNtErrorNoData: // closing named pipe
@ -742,25 +774,23 @@ textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
return 0; //
case kNtErrorAccessDenied: // read doesn't return EACCESS
return ebadf(); //
case kNtErrorOperationAborted:
errno = pt->abort_errno;
return -1;
default:
return __winerr();
}
}
textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen,
int64_t opt_offset) {
static textwindows ssize_t sys_read_nt2(int fd, const struct iovec *iov,
size_t iovlen, int64_t opt_offset,
sigset_t waitmask) {
ssize_t rc;
size_t i, total;
if (opt_offset < -1) return einval();
while (iovlen && !iov[0].iov_len) iov++, iovlen--;
if (iovlen) {
for (total = i = 0; i < iovlen; ++i) {
// TODO(jart): disable cancelations after first iteration
if (!iov[i].iov_len) continue;
rc = sys_read_nt_impl(fd, iov[i].iov_base, iov[i].iov_len, opt_offset);
rc = sys_read_nt_impl(fd, iov[i].iov_base, iov[i].iov_len, opt_offset,
waitmask);
if (rc == -1) {
if (total && errno != ECANCELED) {
return total;
@ -771,11 +801,21 @@ textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen,
total += rc;
if (opt_offset != -1) opt_offset += rc;
if (rc < iov[i].iov_len) break;
waitmask = -1; // disable eintr/ecanceled for remaining iovecs
}
return total;
} else {
return sys_read_nt_impl(fd, NULL, 0, opt_offset);
return sys_read_nt_impl(fd, NULL, 0, opt_offset, waitmask);
}
}
textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen,
int64_t opt_offset) {
ssize_t rc;
sigset_t m = __sig_block();
rc = sys_read_nt2(fd, iov, iovlen, opt_offset, m);
__sig_unblock(m);
return rc;
}
#endif /* __x86_64__ */

View file

@ -58,14 +58,14 @@
* or `SO_RCVTIMEO` is in play and the time interval elapsed
* @raise ENOBUFS is specified by POSIX
* @raise ENXIO is specified by POSIX
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe
*/
ssize_t read(int fd, void *buf, size_t size) {
ssize_t rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (fd < 0) {
rc = ebadf();
@ -87,7 +87,7 @@ ssize_t read(int fd, void *buf, size_t size) {
rc = enosys();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
DATATRACE("read(%d, [%#.*hhs%s], %'zu) → %'zd% m", fd,
(int)MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, rc);
return rc;

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/mem/alloca.h"
@ -35,8 +36,8 @@
#include "libc/str/utf16.h"
#include "libc/sysv/errfuns.h"
textwindows ssize_t sys_readlinkat_nt(int dirfd, const char *path, char *buf,
size_t bufsiz) {
static textwindows ssize_t sys_readlinkat_nt_impl(int dirfd, const char *path,
char *buf, size_t bufsiz) {
char16_t path16[PATH_MAX];
if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1;
@ -128,3 +129,12 @@ textwindows ssize_t sys_readlinkat_nt(int dirfd, const char *path, char *buf,
}
return rc;
}
textwindows ssize_t sys_readlinkat_nt(int dirfd, const char *path, char *buf,
size_t bufsiz) {
ssize_t rc;
BLOCK_SIGNALS;
rc = sys_readlinkat_nt_impl(dirfd, path, buf, bufsiz);
ALLOW_SIGNALS;
return rc;
}

View file

@ -43,12 +43,12 @@
* performance boost in the case of a single small iovec.
*
* @return number of bytes actually read, or -1 w/ errno
* @cancellationpoint
* @cancelationpoint
* @restartable
*/
ssize_t readv(int fd, const struct iovec *iov, int iovlen) {
ssize_t rc;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (fd < 0) {
rc = ebadf();
@ -75,7 +75,7 @@ ssize_t readv(int fd, const struct iovec *iov, int iovlen) {
rc = enosys();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("readv(%d, [%s], %d) → %'ld% m", fd, DescribeIovec(rc, iov, iovlen),
iovlen, rc);
return rc;

209
libc/calls/readwrite-nt.c Normal file
View file

@ -0,0 +1,209 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
struct ReadwriteResources {
int64_t handle;
struct NtOverlapped *overlap;
};
static void UnwindReadwrite(void *arg) {
uint32_t got;
struct ReadwriteResources *rwc = arg;
CancelIoEx(rwc->handle, rwc->overlap);
GetOverlappedResult(rwc->handle, rwc->overlap, &got, true);
CloseHandle(rwc->overlap->hEvent);
}
/**
* Runs code that's common to read/write/pread/pwrite/etc on Windows.
*
* @return bytes exchanged, or -1 w/ errno, or -2 if operation failed
* and caller needs to do more work, examining the GetLastError()
*/
textwindows ssize_t
sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
int64_t handle, uint64_t waitmask,
bool32 ReadOrWriteFile(int64_t, void *, uint32_t, uint32_t *,
struct NtOverlapped *)) {
bool32 ok;
uint64_t m;
uint32_t exchanged;
bool eagained = false;
bool eintered = false;
bool canceled = false;
bool olderror = errno;
struct PosixThread *pt;
struct Fd *f = g_fds.p + fd;
// win32 i/o apis generally take 32-bit values thus we implicitly
// truncate outrageously large sizes. linux actually does it too!
size = MIN(size, 0x7ffff000);
// pread() and pwrite() perform an implicit lseek() operation, so
// similar to the lseek() system call, they too raise ESPIPE when
// operating on a non-seekable file.
bool pwriting = offset != -1;
bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
if (pwriting && !seekable) {
return espipe();
}
// when a file is opened in overlapped mode win32 requires that we
// take over full responsibility for managing our own file pointer
// which is fine, because the one win32 has was never very good in
// the sense that it behaves so differently from linux, that using
// win32 i/o required more compatibilty toil than doing it by hand
if (!pwriting) {
if (seekable) {
offset = f->pointer;
} else {
offset = 0;
}
}
// managing an overlapped i/o operation is tricky to do using just
// imperative procedural logic. its design lends itself more to be
// something that's abstracted in an object-oriented design, which
// easily manages the unusual lifecycles requirements of the thing
// the game here is to not return until win32 is done w/ `overlap`
// next we need to allow signal handlers to re-enter this function
// while we're performing a read in the same thread. this needs to
// be thread safe too of course. read() / write() are cancelation
// points so pthread_cancel() might teleport the execution here to
// pthread_exit(), so we need cleanup handlers that pthread_exit()
// can call, pushed onto the stack, so we don't leak win32 handles
// or worse trash the thread stack containing `overlap` that win32
// temporarily owns while the overlapped i/o op is being performed
// we implement a non-blocking iop by optimistically performing io
// and then aborting the operation if win32 says it needs to block
// with cancelation points, we need to be able to raise eintr when
// this thread is pre-empted to run a signal handler but only when
// that signal handler wasn't installed using this SA_RESTART flag
// in which case read() and write() will keep going afterwards. we
// support a second kind of eintr in cosmo/musl which is ecanceled
// and it's mission critical that it be relayed properly, since it
// can only be returned by a single system call in a thread's life
// another thing we do is check if any pending signals exist, then
// running as many of them as possible before entering a wait call
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
.Pointer = offset};
struct ReadwriteResources rwc = {handle, &overlap};
pthread_cleanup_push(UnwindReadwrite, &rwc);
ok = ReadOrWriteFile(handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
BlockingOperation:
pt = _pthread_self();
pt->pt_iohandle = handle;
pt->pt_ioverlap = &overlap;
pt->pt_flags |= PT_RESTARTABLE;
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_IO, memory_order_release);
m = __sig_beginwait(waitmask);
if (f->flags & O_NONBLOCK) {
CancelIoEx(handle, &overlap);
eagained = true;
} else if (_check_cancel()) {
CancelIoEx(handle, &overlap);
canceled = true;
} else if (_check_signal(true)) {
CancelIoEx(handle, &overlap);
eintered = true;
} else {
WaitForSingleObject(overlap.hEvent, -1u);
}
__sig_finishwait(m);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU,
memory_order_release);
pt->pt_flags &= ~PT_RESTARTABLE;
pt->pt_ioverlap = 0;
pt->pt_iohandle = 0;
ok = true;
}
if (ok) {
bool32 should_wait = canceled || eagained;
ok = GetOverlappedResult(handle, &overlap, &exchanged, should_wait);
if (!ok && GetLastError() == kNtErrorIoIncomplete) {
goto BlockingOperation;
}
}
CloseHandle(overlap.hEvent);
pthread_cleanup_pop(false);
// if we acknowledged a pending masked mode cancelation request then
// we must pass it to the caller immediately now that cleanup's done
if (canceled) {
return ecanceled();
}
// sudden success trumps interrupts and/or failed i/o abort attempts
// plenty of code above might clobber errno, so we always restore it
if (ok) {
if (!pwriting && seekable) {
f->pointer = offset + exchanged;
}
errno = olderror;
return exchanged;
}
// if we backed out of the i/o operation intentionally ignore errors
if (eagained) {
return eagain();
}
// if another thread canceled our win32 i/o operation then we should
// check and see if it was pthread_cancel() which committed the deed
// in which case _check_cancel() can acknowledge the cancelation now
// it's also fine to do nothing here; punt to next cancelation point
if (GetLastError() == kNtErrorOperationAborted && _check_cancel() == -1) {
return ecanceled();
}
// if we chose to process a pending signal earlier then we preserve
// that original error explicitly here even though aborted == eintr
if (eintered) {
return eintr();
}
// read() and write() have generally different error-handling paths
return -2;
}
#endif /* __x86_64__ */

View file

@ -61,9 +61,9 @@ void __restore_tty(void) {
int e;
if (__isrestorable && !__isworker && !__nocolor) {
e = errno;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
sys_write(0, ANSI_RESTORE, __strlen(ANSI_RESTORE));
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
tcsetattr(0, TCSAFLUSH, &__oldtermios);
errno = e;
}

View file

@ -18,11 +18,8 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/console.h"
#include "libc/sysv/errfuns.h"
/**
* Changes process group for process.
@ -33,29 +30,11 @@
* @vforksafe
*/
int setpgid(int pid, int pgid) {
int rc, me;
int rc;
if (!IsWindows()) {
rc = sys_setpgid(pid, pgid);
} else {
me = getpid();
if ((!pid || pid == me) && (!pgid || pgid == me)) {
// "When a process is created with kNtCreateNewProcessGroup
// specified, an implicit call to SetConsoleCtrlHandler(NULL,TRUE)
// is made on behalf of the new process; this means that the new
// process has CTRL+C disabled. This lets shells handle CTRL+C
// themselves, and selectively pass that signal on to
// sub-processes. CTRL+BREAK is not disabled, and may be used to
// interrupt the process/process group."
// ──Quoth MSDN § CreateProcessW()
if (SetConsoleCtrlHandler(0, 1)) {
rc = 0;
} else {
rc = __winerr();
}
} else {
// avoid bash printing scary warnings for now
rc = 0;
}
rc = 0; // not sure what to do on windows yet
}
STRACE("setpgid(%d, %d) → %d% m", pid, pgid, rc);
return rc;

View file

@ -18,9 +18,8 @@
*/
#include "libc/sysv/consts/sig.h"
#include "ape/sections.internal.h"
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
@ -56,10 +55,14 @@
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/ss.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
/**
* @fileoverview Cosmopolitan Signals for Windows.
*/
struct SignalFrame {
struct PosixThread *pt;
struct NtContext *nc;
@ -86,6 +89,17 @@ textwindows bool __sig_ignored(int sig) {
__sig_ignored_by_default(sig));
}
textwindows void __sig_delete(int sig) {
struct Dll *e;
__sig.pending &= ~(1ull << (sig - 1));
_pthread_lock();
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
pt->tib->tib_sigpending &= ~(1ull << (sig - 1));
}
_pthread_unlock();
}
static textwindows bool __sig_should_use_altstack(unsigned flags,
struct CosmoTib *tib) {
if (!(flags & SA_ONSTACK)) {
@ -141,53 +155,72 @@ static textwindows sigaction_f __sig_handler(unsigned rva) {
textwindows int __sig_raise(int sig, int sic) {
unsigned rva, flags;
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
ucontext_t ctx = {.uc_sigmask.__bits[0] = tib->tib_sigmask};
struct PosixThread *pt = _pthread_self();
ucontext_t ctx = {.uc_sigmask = pt->tib->tib_sigmask};
if (!__sig_start(pt, sig, &rva, &flags)) return 0;
siginfo_t si = {.si_signo = sig, .si_code = sic};
struct NtContext nc;
nc.ContextFlags = kNtContextAll;
GetThreadContext(GetCurrentThread(), &nc);
_ntcontext2linux(&ctx, &nc);
pt->tib->tib_sigmask |= __sighandmask[sig];
if (!(flags & SA_NODEFER)) {
tib->tib_sigmask |= 1ull << (sig - 1);
pt->tib->tib_sigmask |= 1ull << (sig - 1);
}
STRACE("entering raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, &(sigset_t){{pt->tib->tib_sigmask}}));
NTTRACE("entering raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
STRACE("leaving raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva),
DescribeSigset(0, &(sigset_t){{pt->tib->tib_sigmask}}),
DescribeSigset(0, &ctx.uc_sigmask));
tib->tib_sigmask = ctx.uc_sigmask.__bits[0];
NTTRACE("leaving raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva),
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask),
DescribeSigset(0, &ctx.uc_sigmask));
pt->tib->tib_sigmask = ctx.uc_sigmask;
return (flags & SA_RESTART) ? 2 : 1;
}
textwindows void __sig_cancel(struct PosixThread *pt, unsigned flags) {
atomic_int *futex;
if (_weaken(WakeByAddressSingle) &&
(futex = atomic_load_explicit(&pt->pt_futex, memory_order_acquire))) {
STRACE("canceling futex");
_weaken(WakeByAddressSingle)(futex);
} else if (!(flags & SA_RESTART) && pt->iohandle > 0) {
STRACE("canceling i/o operation");
if (!CancelIoEx(pt->iohandle, pt->ioverlap)) {
int err = GetLastError();
if (err != kNtErrorNotFound) {
STRACE("CancelIoEx() failed w/ %d", err);
}
}
} else if (pt->pt_flags & PT_INSEMAPHORE) {
STRACE("canceling semaphore");
pt->pt_flags &= ~PT_INSEMAPHORE;
if (!ReleaseSemaphore(pt->semaphore, 1, 0)) {
STRACE("ReleaseSemaphore() failed w/ %d", GetLastError());
}
} else {
STRACE("canceling asynchronously");
// cancels blocking operations being performed by signaled thread
textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) {
bool should_restart;
atomic_int *blocker;
// cancelation points need to set pt_blocker before entering a wait op
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
// cancelation points should set it back to this after blocking
// however, code that longjmps might mess it up a tolerable bit
if (blocker == PT_BLOCKER_CPU) {
STRACE("%G delivered to %d asynchronously", sig, _pthread_tid(pt));
return;
}
// most cancelation points can be safely restarted w/o raising eintr
should_restart = (flags & SA_RESTART) && (pt->pt_flags & PT_RESTARTABLE);
// we can cancel another thread's overlapped i/o op after the freeze
if (blocker == PT_BLOCKER_IO) {
if (should_restart) {
STRACE("%G restarting %d's i/o op", sig, _pthread_tid(pt));
} else {
STRACE("%G interupting %d's i/o op", sig, _pthread_tid(pt));
CancelIoEx(pt->pt_iohandle, pt->pt_ioverlap);
}
return;
}
// threads can create semaphores on an as-needed basis
if (blocker == PT_BLOCKER_SEM) {
if (should_restart) {
STRACE("%G restarting %d's semaphore", sig, _pthread_tid(pt));
} else {
STRACE("%G releasing %d's semaphore", sig, _pthread_tid(pt));
pt->pt_flags &= ~PT_RESTARTABLE;
ReleaseSemaphore(pt->pt_semaphore, 1, 0);
}
return;
}
// all other blocking ops that aren't overlap should use futexes
// we force restartable futexes to churn by waking w/o releasing
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
if (!should_restart) {
atomic_store_explicit(blocker, 1, memory_order_release);
}
WakeByAddressSingle(blocker);
}
static textwindows wontreturn void __sig_panic(const char *msg) {
@ -207,23 +240,21 @@ static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
ucontext_t ctx = {0};
int sig = sf->si.si_signo;
_ntcontext2linux(&ctx, sf->nc);
ctx.uc_sigmask.__bits[0] =
atomic_load_explicit(&sf->pt->tib->tib_sigmask, memory_order_acquire);
ctx.uc_sigmask = sf->pt->tib->tib_sigmask;
sf->pt->tib->tib_sigmask |= __sighandmask[sig];
if (!(sf->flags & SA_NODEFER)) {
sf->pt->tib->tib_sigmask |= 1ull << (sig - 1);
}
++__sig.count;
STRACE("entering __sig_tramp(%G, %t) with mask %s → %s", sig,
__sig_handler(sf->rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, &(sigset_t){{sf->pt->tib->tib_sigmask}}));
NTTRACE("entering __sig_tramp(%G, %t) with mask %s → %s", sig,
__sig_handler(sf->rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask));
__sig_handler(sf->rva)(sig, &sf->si, &ctx);
STRACE("leaving __sig_tramp(%G, %t) with mask %s → %s", sig,
__sig_handler(sf->rva),
DescribeSigset(0, &(sigset_t){{sf->pt->tib->tib_sigmask}}),
DescribeSigset(0, &ctx.uc_sigmask));
atomic_store_explicit(&sf->pt->tib->tib_sigmask, ctx.uc_sigmask.__bits[0],
memory_order_release);
// TDOO(jart): Do we need to do a __sig_check() here?
NTTRACE("leaving __sig_tramp(%G, %t) with mask %s → %s", sig,
__sig_handler(sf->rva),
DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask),
DescribeSigset(0, &ctx.uc_sigmask));
sf->pt->tib->tib_sigmask = ctx.uc_sigmask;
_ntlinux2context(sf->nc, &ctx);
SetThreadContext(GetCurrentThread(), sf->nc);
__sig_panic("SetThreadContext(GetCurrentThread)");
@ -275,18 +306,15 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
return ESRCH;
}
ResumeThread(th); // doesn't actually resume if doing blocking i/o
pt->abort_errno = EINTR;
__sig_cancel(pt, flags); // we can wake it up immediately by canceling it
ResumeThread(th);
__sig_cancel(pt, sig, flags);
return 0;
}
textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
int rc;
BLOCK_SIGNALS;
BLOCK_CANCELLATIONS;
rc = __sig_killer(pt, sig, sic);
ALLOW_CANCELLATIONS;
ALLOW_SIGNALS;
return rc;
}
@ -302,22 +330,25 @@ textwindows void __sig_generate(int sig, int sic) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
pthread_spin_lock(&_pthread_lock);
BLOCK_SIGNALS;
_pthread_lock();
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
pt = POSIXTHREAD_CONTAINER(e);
if (atomic_load_explicit(&pt->status, memory_order_acquire) <
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) <
kPosixThreadTerminated &&
!(pt->tib->tib_sigmask & (1ull << (sig - 1)))) {
mark = pt;
break;
}
}
pthread_spin_unlock(&_pthread_lock);
_pthread_unlock();
ALLOW_SIGNALS;
if (mark) {
STRACE("generating %G by killing %d", sig, _pthread_tid(mark));
__sig_kill(mark, sig, sic);
} else {
STRACE("all threads block %G so adding to pending signals of process", sig);
__sig.pending |= sig;
__sig.pending |= 1ull << (sig - 1);
}
}
@ -380,8 +411,8 @@ static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
}
}
static void __sig_crasher(struct NtExceptionPointers *ep, int code, int sig,
struct CosmoTib *tib) {
static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
struct CosmoTib *tib) {
// increment the signal count for getrusage()
++__sig.count;
@ -436,9 +467,13 @@ static void __sig_crasher(struct NtExceptionPointers *ep, int code, int sig,
ucontext_t ctx = {0};
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
_ntcontext2linux(&ctx, ep->ContextRecord);
ctx.uc_sigmask.__bits[0] = tib->tib_sigmask;
ctx.uc_sigmask = tib->tib_sigmask;
tib->tib_sigmask |= __sighandmask[sig];
if (!(flags & SA_NODEFER)) {
tib->tib_sigmask |= 1ull << (sig - 1);
}
__sig_handler(rva)(sig, &si, &ctx);
tib->tib_sigmask = ctx.uc_sigmask.__bits[0];
tib->tib_sigmask = ctx.uc_sigmask;
_ntlinux2context(ep->ContextRecord, &ctx);
}
@ -470,10 +505,10 @@ __msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
struct CosmoTib *tib = __get_tls();
unsigned flags = __sighandflags[sig];
if (__sig_should_use_altstack(flags, tib)) {
__stack_call(ep, code, sig, tib, __sig_crasher,
__stack_call(ep, code, sig, tib, __sig_unmaskable,
tib->tib_sigstack_addr + tib->tib_sigstack_size);
} else {
__sig_crasher(ep, code, sig, tib);
__sig_unmaskable(ep, code, sig, tib);
}
// resume running user program
@ -517,6 +552,7 @@ static textwindows int __sig_checkem(atomic_ulong *sigs, struct CosmoTib *tib,
for (int sig = 1; sig <= 64; ++sig) {
if ((deliverable & (1ull << (sig - 1))) &&
atomic_fetch_and(sigs, ~(1ull << (sig - 1))) & (1ull << (sig - 1))) {
STRACE("found pending %G we can raise now", sig);
handler_was_called |= __sig_raise(sig, SI_KERNEL);
}
}

View file

@ -26,7 +26,7 @@ int __sig_check(void);
int __sig_kill(struct PosixThread *, int, int);
int __sig_mask(int, const sigset_t *, sigset_t *);
int __sig_raise(int, int);
void __sig_cancel(struct PosixThread *, unsigned);
void __sig_delete(int);
void __sig_generate(int, int);
void __sig_init(void);

View file

@ -19,7 +19,6 @@
#include "libc/calls/struct/sigaction.h"
#include "ape/sections.internal.h"
#include "libc/assert.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
@ -27,7 +26,6 @@
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigaction.internal.h"
#include "libc/calls/struct/siginfo.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/calls/ucontext.h"
@ -35,6 +33,7 @@
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/log/backtrace.internal.h"
@ -47,56 +46,53 @@
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#if SupportsWindows()
__static_yoink("__sig_ctor");
#endif
#define SA_RESTORER 0x04000000
static void sigaction_cosmo2native(union metasigaction *sa) {
void *handler;
uint64_t flags;
void *restorer;
uint32_t mask[4];
uint32_t masklo;
uint32_t maskhi;
if (!sa) return;
flags = sa->cosmo.sa_flags;
handler = sa->cosmo.sa_handler;
restorer = sa->cosmo.sa_restorer;
mask[0] = sa->cosmo.sa_mask.__bits[0];
mask[1] = sa->cosmo.sa_mask.__bits[0] >> 32;
mask[2] = sa->cosmo.sa_mask.__bits[1];
mask[3] = sa->cosmo.sa_mask.__bits[1] >> 32;
masklo = sa->cosmo.sa_mask;
maskhi = sa->cosmo.sa_mask >> 32;
if (IsLinux()) {
sa->linux.sa_flags = flags;
sa->linux.sa_handler = handler;
sa->linux.sa_restorer = restorer;
sa->linux.sa_mask[0] = mask[0];
sa->linux.sa_mask[1] = mask[1];
sa->linux.sa_mask[0] = masklo;
sa->linux.sa_mask[1] = maskhi;
} else if (IsXnu()) {
sa->xnu_in.sa_flags = flags;
sa->xnu_in.sa_handler = handler;
sa->xnu_in.sa_restorer = restorer;
sa->xnu_in.sa_mask[0] = mask[0];
sa->xnu_in.sa_mask[0] = masklo;
} else if (IsFreebsd()) {
sa->freebsd.sa_flags = flags;
sa->freebsd.sa_handler = handler;
sa->freebsd.sa_mask[0] = mask[0];
sa->freebsd.sa_mask[1] = mask[1];
sa->freebsd.sa_mask[2] = mask[2];
sa->freebsd.sa_mask[3] = mask[3];
sa->freebsd.sa_mask[0] = masklo;
sa->freebsd.sa_mask[1] = maskhi;
sa->freebsd.sa_mask[2] = 0;
sa->freebsd.sa_mask[3] = 0;
} else if (IsOpenbsd()) {
sa->openbsd.sa_flags = flags;
sa->openbsd.sa_handler = handler;
sa->openbsd.sa_mask[0] = mask[0];
sa->openbsd.sa_mask[0] = masklo;
} else if (IsNetbsd()) {
sa->netbsd.sa_flags = flags;
sa->netbsd.sa_handler = handler;
sa->netbsd.sa_mask[0] = mask[0];
sa->netbsd.sa_mask[1] = mask[1];
sa->netbsd.sa_mask[2] = mask[2];
sa->netbsd.sa_mask[3] = mask[3];
sa->netbsd.sa_mask[0] = masklo;
sa->netbsd.sa_mask[1] = maskhi;
sa->netbsd.sa_mask[2] = 0;
sa->netbsd.sa_mask[3] = 0;
}
}
@ -104,44 +100,40 @@ static void sigaction_native2cosmo(union metasigaction *sa) {
void *handler;
uint64_t flags;
void *restorer = 0;
uint32_t mask[4] = {0};
uint32_t masklo;
uint32_t maskhi = 0;
if (!sa) return;
if (IsLinux()) {
flags = sa->linux.sa_flags;
handler = sa->linux.sa_handler;
restorer = sa->linux.sa_restorer;
mask[0] = sa->linux.sa_mask[0];
mask[1] = sa->linux.sa_mask[1];
masklo = sa->linux.sa_mask[0];
maskhi = sa->linux.sa_mask[1];
} else if (IsXnu()) {
flags = sa->xnu_out.sa_flags;
handler = sa->xnu_out.sa_handler;
mask[0] = sa->xnu_out.sa_mask[0];
masklo = sa->xnu_out.sa_mask[0];
} else if (IsFreebsd()) {
flags = sa->freebsd.sa_flags;
handler = sa->freebsd.sa_handler;
mask[0] = sa->freebsd.sa_mask[0];
mask[1] = sa->freebsd.sa_mask[1];
mask[2] = sa->freebsd.sa_mask[2];
mask[3] = sa->freebsd.sa_mask[3];
masklo = sa->freebsd.sa_mask[0];
maskhi = sa->freebsd.sa_mask[1];
} else if (IsOpenbsd()) {
flags = sa->openbsd.sa_flags;
handler = sa->openbsd.sa_handler;
mask[0] = sa->openbsd.sa_mask[0];
masklo = sa->openbsd.sa_mask[0];
} else if (IsNetbsd()) {
flags = sa->netbsd.sa_flags;
handler = sa->netbsd.sa_handler;
mask[0] = sa->netbsd.sa_mask[0];
mask[1] = sa->netbsd.sa_mask[1];
mask[2] = sa->netbsd.sa_mask[2];
mask[3] = sa->netbsd.sa_mask[3];
masklo = sa->netbsd.sa_mask[0];
maskhi = sa->netbsd.sa_mask[1];
} else {
return;
}
sa->cosmo.sa_flags = flags;
sa->cosmo.sa_handler = handler;
sa->cosmo.sa_restorer = restorer;
sa->cosmo.sa_mask.__bits[0] = mask[0] | (uint64_t)mask[1] << 32;
sa->cosmo.sa_mask.__bits[1] = mask[2] | (uint64_t)mask[3] << 32;
sa->cosmo.sa_mask = masklo | (uint64_t)maskhi << 32;
}
static int __sigaction(int sig, const struct sigaction *act,
@ -257,14 +249,10 @@ static int __sigaction(int sig, const struct sigaction *act,
if (rc != -1 && !__vforked) {
if (act) {
__sighandrvas[sig] = rva;
__sighandmask[sig] = act->sa_mask;
__sighandflags[sig] = act->sa_flags;
if (IsWindows()) {
if (__sig_ignored(sig)) {
__sig.pending &= ~(1ull << (sig - 1));
if (__tls_enabled) {
__get_tls()->tib_sigpending &= ~(1ull << (sig - 1));
}
}
if (IsWindows() && __sig_ignored(sig)) {
__sig_delete(sig);
}
}
}
@ -486,7 +474,7 @@ static int __sigaction(int sig, const struct sigaction *act,
* handler doesn't save / restore the `errno` global when calling wait,
* then any i/o logic in the main program that checks `errno` will most
* likely break. This is rare in practice, since systems usually design
* signals to favor delivery from cancellation points before they block
* signals to favor delivery from cancelation points before they block
* however that's not guaranteed.
*
* @return 0 on success or -1 w/ errno

View file

@ -57,8 +57,7 @@ privileged void __sigenter_freebsd(int sig, struct siginfo_freebsd *freebsdinfo,
g.uc.uc_stack.ss_sp = ctx->uc_stack.ss_sp;
g.uc.uc_stack.ss_size = ctx->uc_stack.ss_size;
g.uc.uc_stack.ss_flags = ctx->uc_stack.ss_flags;
__repmovsb(&g.uc.uc_sigmask, ctx->uc_sigmask,
MIN(sizeof(g.uc.uc_sigmask), sizeof(ctx->uc_sigmask)));
g.uc.uc_sigmask = ctx->uc_sigmask[0] | (uint64_t)ctx->uc_sigmask[0] << 32;
g.uc.uc_mcontext.r8 = ctx->uc_mcontext.mc_r8;
g.uc.uc_mcontext.r9 = ctx->uc_mcontext.mc_r9;
g.uc.uc_mcontext.r10 = ctx->uc_mcontext.mc_r10;
@ -88,8 +87,8 @@ privileged void __sigenter_freebsd(int sig, struct siginfo_freebsd *freebsdinfo,
ctx->uc_stack.ss_size = g.uc.uc_stack.ss_size;
ctx->uc_stack.ss_flags = g.uc.uc_stack.ss_flags;
ctx->uc_flags = g.uc.uc_flags;
__repmovsb(ctx->uc_sigmask, &g.uc.uc_sigmask,
MIN(sizeof(g.uc.uc_sigmask), sizeof(ctx->uc_sigmask)));
ctx->uc_sigmask[0] = g.uc.uc_sigmask;
ctx->uc_sigmask[1] = g.uc.uc_sigmask >> 32;
ctx->uc_mcontext.mc_rdi = g.uc.uc_mcontext.rdi;
ctx->uc_mcontext.mc_rsi = g.uc.uc_mcontext.rsi;
ctx->uc_mcontext.mc_rdx = g.uc.uc_mcontext.rdx;

View file

@ -55,8 +55,7 @@ privileged void __sigenter_openbsd(int sig, struct siginfo_openbsd *openbsdinfo,
__repstosb(&g.uc, 0, sizeof(g.uc));
__siginfo2cosmo(&g.si, (void *)openbsdinfo);
g.uc.uc_mcontext.fpregs = &g.uc.__fpustate;
g.uc.uc_sigmask.__bits[0] = ctx->sc_mask;
g.uc.uc_sigmask.__bits[1] = 0;
g.uc.uc_sigmask = ctx->sc_mask;
g.uc.uc_mcontext.rdi = ctx->sc_rdi;
g.uc.uc_mcontext.rsi = ctx->sc_rsi;
g.uc.uc_mcontext.rdx = ctx->sc_rdx;
@ -83,7 +82,7 @@ privileged void __sigenter_openbsd(int sig, struct siginfo_openbsd *openbsdinfo,
sizeof(*ctx->sc_fpstate));
}
((sigaction_f)(__executable_start + rva))(sig, &g.si, &g.uc);
ctx->sc_mask = g.uc.uc_sigmask.__bits[0];
ctx->sc_mask = g.uc.uc_sigmask;
ctx->sc_rdi = g.uc.uc_mcontext.rdi;
ctx->sc_rsi = g.uc.uc_mcontext.rsi;
ctx->sc_rdx = g.uc.uc_mcontext.rdx;

View file

@ -507,8 +507,7 @@ privileged void __sigenter_xnu(void *fn, int infostyle, int sig,
__repstosb(&g, 0, sizeof(g));
if (xnuctx) {
g.uc.uc_sigmask.__bits[0] = xnuctx->uc_sigmask;
g.uc.uc_sigmask.__bits[1] = 0;
g.uc.uc_sigmask = xnuctx->uc_sigmask;
g.uc.uc_stack.ss_sp = xnuctx->uc_stack.ss_sp;
g.uc.uc_stack.ss_flags = xnuctx->uc_stack.ss_flags;
g.uc.uc_stack.ss_size = xnuctx->uc_stack.ss_size;
@ -546,7 +545,7 @@ privileged void __sigenter_xnu(void *fn, int infostyle, int sig,
xnuctx->uc_stack.ss_sp = g.uc.uc_stack.ss_sp;
xnuctx->uc_stack.ss_flags = g.uc.uc_stack.ss_flags;
xnuctx->uc_stack.ss_size = g.uc.uc_stack.ss_size;
xnuctx->uc_sigmask = g.uc.uc_sigmask.__bits[0];
xnuctx->uc_sigmask = g.uc.uc_sigmask;
#ifdef __x86_64__
if (xnuctx->uc_mcontext) {
if (xnuctx->uc_mcsize >=

View file

@ -41,24 +41,19 @@ int sigpending(sigset_t *pending) {
rc = efault();
} else if (IsLinux() || IsNetbsd() || IsOpenbsd() || IsFreebsd() || IsXnu()) {
// 128 signals on NetBSD and FreeBSD, 64 on Linux, 32 on OpenBSD and XNU
rc = sys_sigpending(pending, 8);
// OpenBSD passes signal sets in words rather than pointers
uint64_t mem[2];
rc = sys_sigpending(mem, 8);
if (IsOpenbsd()) {
pending->__bits[0] = (unsigned)rc;
rc = 0;
*pending = rc;
} else {
*pending = mem[0];
}
// only modify memory on success
if (!rc) {
// clear unsupported bits
if (IsXnu()) {
pending->__bits[0] &= 0xFFFFFFFF;
}
if (IsLinux() || IsOpenbsd() || IsXnu()) {
pending->__bits[1] = 0;
}
if (IsXnu() || IsOpenbsd()) {
*pending &= 0xffffffff;
}
rc = 0;
} else if (IsWindows()) {
*pending = (sigset_t){{__sig.pending | __get_tls()->tib_sigpending}};
*pending = __sig.pending | __get_tls()->tib_sigpending;
rc = 0;
} else {
rc = enosys();

View file

@ -1,33 +0,0 @@
/*-*- 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 2022 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/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/dce.h"
#include "libc/sysv/consts/sig.h"
sigset_t _sigsetmask(sigset_t neu) {
sigset_t res;
if (IsMetal() || IsWindows()) {
__sig_mask(SIG_SETMASK, &neu, &res);
} else {
sys_sigprocmask(SIG_SETMASK, &neu, &res);
}
return res;
}

View file

@ -24,10 +24,12 @@
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/tls.h"
/**
* Blocks until SIG MASK is delivered to thread.
@ -38,47 +40,29 @@
* @param ignore is a bitset of signals to block temporarily, which if
* NULL is equivalent to passing an empty signal set
* @return -1 w/ EINTR (or possibly EFAULT)
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/
int sigsuspend(const sigset_t *ignore) {
int rc;
const sigset_t *arg;
sigset_t save, mask = {0};
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
if (IsAsan() && ignore && !__asan_is_valid(ignore, sizeof(*ignore))) {
rc = efault();
} else if (IsXnu() || IsOpenbsd()) {
// openbsd and xnu only support 32 signals
// they use a register calling convention for sigsuspend
if (ignore) {
arg = (sigset_t *)(uintptr_t)(*(uint32_t *)ignore);
} else {
arg = 0;
}
rc = sys_sigsuspend(arg, 8);
} else if (IsLinux() || IsFreebsd() || IsNetbsd() || IsWindows()) {
if (ignore) {
arg = ignore;
} else {
arg = &mask;
}
if (!IsWindows()) {
rc = sys_sigsuspend(arg, 8);
} else {
__sig_mask(SIG_SETMASK, arg, &save);
while (!(rc = _check_interrupts(0))) {
if ((rc = __pause_thread(__SIG_SIG_INTERVAL_MS))) break;
}
__sig_mask(SIG_SETMASK, &save, 0);
}
// openbsd and xnu use a 32 signal register convention
rc = sys_sigsuspend(ignore ? (void *)(intptr_t)(uint32_t)*ignore : 0, 8);
} else {
rc = enosys();
sigset_t waitmask = ignore ? *ignore : 0;
if (IsWindows() || IsMetal()) {
while (!(rc = _park_norestart(-1u, waitmask))) donothing;
} else {
rc = sys_sigsuspend((uint64_t[2]){waitmask}, 8);
}
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("sigsuspend(%s) → %d% m", DescribeSigset(0, ignore), rc);
return rc;
}

View file

@ -42,7 +42,7 @@
* @raise EAGAIN if deadline expired
* @raise ENOSYS on Windows, XNU, OpenBSD, Metal
* @raise EFAULT if invalid memory was supplied
* @cancellationpoint
* @cancelationpoint
*/
int sigtimedwait(const sigset_t *set, siginfo_t *info,
const struct timespec *timeout) {
@ -50,7 +50,7 @@ int sigtimedwait(const sigset_t *set, siginfo_t *info,
char strsig[21];
struct timespec ts;
union siginfo_meta si = {0};
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
(void)strsig;
if (IsAsan() && (!__asan_is_valid(set, sizeof(*set)) ||
@ -73,7 +73,7 @@ int sigtimedwait(const sigset_t *set, siginfo_t *info,
rc = enosys();
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("sigtimedwait(%s, [%s], %s) → %s% m", DescribeSigset(0, set),
DescribeSiginfo(rc, info), DescribeTimespec(0, timeout),
strsignal_r(rc, strsig));

View file

@ -28,7 +28,7 @@
* @raise ECANCELED if thread was cancelled in masked mode
* @raise ENOSYS on OpenBSD, XNU, and Windows
* @see sigtimedwait()
* @cancellationpoint
* @cancelationpoint
*/
int sigwaitinfo(const sigset_t *mask, siginfo_t *si) {
return sigtimedwait(mask, si, 0);

View file

@ -32,7 +32,7 @@
* using the ceiling function, and finally `-1u` may be returned if
* thread was cancelled with `PTHREAD_CANCEL_MASKED` in play
* @see clock_nanosleep()
* @cancellationpoint
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_STATE_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_STATE_INTERNAL_H_
#include "libc/calls/calls.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@ -9,6 +10,7 @@ extern int __vforked;
extern pthread_mutex_t __fds_lock_obj;
extern unsigned __sighandrvas[NSIG + 1];
extern unsigned __sighandflags[NSIG + 1];
extern uint64_t __sighandmask[NSIG + 1];
extern const struct NtSecurityAttributes kNtIsInheritable;
void __fds_wipe(void);

View file

@ -16,7 +16,9 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/statfs.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/createfile.h"
@ -31,14 +33,19 @@ textwindows int sys_statfs_nt(const char *path, struct statfs *sf) {
int64_t h;
char16_t path16[PATH_MAX];
if (__mkntpath(path, path16) == -1) return -1;
BLOCK_SIGNALS;
h = __fix_enotdir(
CreateFile(path16, kNtFileGenericRead,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0,
kNtOpenExisting,
kNtFileAttributeNormal | kNtFileFlagBackupSemantics, 0),
path16);
if (h == -1) return -1;
rc = sys_fstatfs_nt(h, sf);
CloseHandle(h);
if (h != -1) {
rc = sys_fstatfs_nt(h, sf);
CloseHandle(h);
} else {
rc = -1;
}
ALLOW_SIGNALS;
return rc;
}

View file

@ -38,7 +38,7 @@
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if signal was delivered
* @raise ENOTSUP if /zip path
* @cancellationpoint
* @cancelationpoint
*/
int statfs(const char *path, struct statfs *sf) {
#pragma GCC push_options
@ -48,7 +48,7 @@ int statfs(const char *path, struct statfs *sf) {
#pragma GCC pop_options
int rc;
struct ZiposUri zipname;
BEGIN_CANCELLATION_POINT;
BEGIN_CANCELATION_POINT;
CheckLargeStackAllocation(&m, sizeof(m));
if (_weaken(__zipos_parseuri) &&
@ -62,7 +62,7 @@ int statfs(const char *path, struct statfs *sf) {
rc = sys_statfs_nt(path, sf);
}
END_CANCELLATION_POINT;
END_CANCELATION_POINT;
STRACE("statfs(%#s, [%s]) → %d% m", path, DescribeStatfs(rc, sf));
return rc;
}

View file

@ -1,18 +1,11 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_
#include "libc/nt/struct/overlapped.h"
#include "libc/thread/thread.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define DO_NOTHING 0
#define DO_RESTART 1
#define DO_EINTR 2
#define kFdEmpty 0
#define kFdFile 1
#define kFdSocket 2
#define kFdProcess 3
#define kFdConsole 4
#define kFdSerial 5
#define kFdZip 6
@ -21,31 +14,21 @@ COSMOPOLITAN_C_START_
struct Fd {
char kind;
bool eoftty;
bool dontclose;
unsigned flags;
unsigned mode;
int64_t handle;
int64_t extra;
int64_t pointer;
pthread_mutex_t lock;
};
struct StdinRelay {
_Atomic(uint32_t) once;
int64_t inisem; /* semaphore to delay 1st read */
int64_t handle; /* should == g_fds.p[0].handle */
int64_t reader; /* ReadFile() use this instead */
int64_t writer; /* only used by WinStdinThread */
int64_t thread; /* handle for the stdio thread */
struct NtOverlapped overlap;
int family;
int type;
int protocol;
uint32_t rcvtimeo;
uint32_t sndtimeo;
};
struct Fds {
_Atomic(int) f; /* lowest free slot */
size_t n;
struct Fd *p, *e;
struct StdinRelay stdin;
};
COSMOPOLITAN_C_END_

View file

@ -8,14 +8,14 @@ COSMOPOLITAN_C_START_
typedef void (*sighandler_t)(int);
typedef void (*sigaction_f)(int, struct siginfo *, void *);
struct sigaction { /* cosmo abi */
struct sigaction {
union {
sighandler_t sa_handler;
sigaction_f sa_sigaction;
};
uint64_t sa_flags;
void (*sa_restorer)(void);
struct sigset sa_mask;
sigset_t sa_mask;
};
sighandler_t signal(int, sighandler_t);

View file

@ -3,9 +3,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
typedef struct sigset {
uint64_t __bits[2];
} sigset_t;
typedef uint64_t sigset_t;
int sigaddset(sigset_t *, int) paramsnonnull();
int sigdelset(sigset_t *, int) paramsnonnull();
@ -20,8 +18,6 @@ int sigprocmask(int, const sigset_t *, sigset_t *);
int sigsuspend(const sigset_t *);
int sigpending(sigset_t *);
int pthread_sigmask(int, const sigset_t *, sigset_t *);
sigset_t _sigsetmask(sigset_t);
sigset_t _sigblockall(void);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -5,10 +5,25 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int __sys_sigprocmask(int, const struct sigset *, struct sigset *, uint64_t);
int sys_sigprocmask(int, const struct sigset *, struct sigset *);
int sys_sigsuspend(const struct sigset *, uint64_t);
int sys_sigpending(struct sigset *, size_t);
#define BLOCK_SIGNALS \
do { \
sigset_t _SigMask; \
_SigMask = __sig_block()
#define ALLOW_SIGNALS \
__sig_unblock(_SigMask); \
} \
while (0)
int __sig_enqueue(int);
sigset_t __sig_block(void);
void __sig_unblock(sigset_t);
void __sig_finishwait(sigset_t);
sigset_t __sig_beginwait(sigset_t);
int __sys_sigprocmask(int, const uint64_t *, uint64_t *, uint64_t);
int sys_sigprocmask(int, const sigset_t *, sigset_t *);
int sys_sigsuspend(const uint64_t *, uint64_t);
int sys_sigpending(uint64_t *, size_t);
const char *DescribeSigset(char[128], int, const sigset_t *);
#define DescribeSigset(rc, ss) DescribeSigset(alloca(128), rc, ss)

View file

@ -33,7 +33,7 @@ struct ucontext_openbsd {
int64_t sc_ss;
struct FpuState *sc_fpstate;
int32_t __sc_unused;
int32_t sc_mask;
uint32_t sc_mask;
int64_t sc_cookie;
};

View file

@ -18,6 +18,7 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/createfile.h"
@ -43,6 +44,7 @@ textwindows int sys_sync_nt(void) {
if (!(drives & (1 << i))) continue;
path[4] = 'A' + i;
if (ntaccesscheck(path, R_OK | W_OK) != -1) {
BLOCK_SIGNALS;
if ((volume = CreateFile(
path, kNtFileReadAttributes,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0,
@ -50,6 +52,7 @@ textwindows int sys_sync_nt(void) {
FlushFileBuffers(volume);
CloseHandle(volume);
}
ALLOW_SIGNALS;
}
}
return 0;

Some files were not shown because too many files have changed in this diff Show more