Improve Windows Console I/O

- Blocking read operations on the Windows Console can now EINTR
- Blocking read operations on Windows pipes now EINTR more reliably
- setitimer() will no longer be inherited across fork() on Windows
- It's now possible to use ECHO when the console is in raw mode
- The ECHOCTL flag now works correctly on the Windows Console
- The ICRNL flag now works correctly on the Windows Console
- pread() and pwrite() will now raise ESPIPE on Windows
- Opening /dev/tty on Windows is improved (untested)
- Overlapped I/O is now implemented in a better way
This commit is contained in:
Justine Tunney 2023-08-08 04:00:29 -07:00
parent decf216655
commit 33d280c8ba
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
34 changed files with 580 additions and 376 deletions

View file

@ -17,28 +17,24 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/macros.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/time/time.h"
/**
* Asks for single-shot SIGALRM to be raise()'d after interval.
*
* @param seconds until we get signal, or 0 to reset previous alarm()
* @return seconds previous alarm() had remaining, or -1u w/ errno
* @see setitimer()
* @param seconds is how long to wait before raising SIGALRM (which will
* only happen once) or zero to clear any previously scheduled alarm
* @return seconds that were remaining on the previously scheduled
* alarm, or zero if there wasn't one (failure isn't possible)
* @see setitimer() for a more powerful api
* @asyncsignalsafe
*/
unsigned alarm(unsigned seconds) {
struct itimerval it;
bzero(&it, sizeof(it));
it.it_value.tv_sec = seconds;
npassert(!setitimer(ITIMER_REAL, &it, &it));
if (!it.it_value.tv_sec && !it.it_value.tv_usec) {
return 0;
} else {
return MIN(1, it.it_value.tv_sec + (it.it_value.tv_usec > 5000000));
}
struct itimerval it, old;
it.it_value = timeval_fromseconds(seconds);
it.it_interval = timeval_zero;
npassert(!setitimer(ITIMER_REAL, &it, &old));
return timeval_toseconds(old.it_value);
}

View file

@ -41,9 +41,15 @@ textwindows int _check_interrupts(int sigops, struct Fd *fd) {
errno = rc;
return -1;
}
if (__tls_enabled) {
__get_tls()->tib_flags |= TIB_FLAG_TIME_CRITICAL;
}
if (_weaken(_check_sigalrm)) {
_weaken(_check_sigalrm)();
}
if (__tls_enabled) {
__get_tls()->tib_flags &= ~TIB_FLAG_TIME_CRITICAL;
}
if (!__tls_enabled || !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) {
if (!(sigops & kSigOpNochld) && _weaken(_check_sigchld)) {
_weaken(_check_sigchld)();

View file

@ -23,21 +23,28 @@
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/o.h"
static textwindows int sys_open_nt_impl(int dirfd, const char *path,
uint32_t flags, int32_t mode) {
static textwindows int64_t sys_open_nt_impl(int dirfd, const char *path,
uint32_t flags, int32_t mode,
uint32_t extra_attr) {
char16_t path16[PATH_MAX];
uint32_t perm, share, disp, attr;
if (__mkntpathat(dirfd, path, flags, path16) == -1) return -1;
if (GetNtOpenFlags(flags, mode, &perm, &share, &disp, &attr) == -1) return -1;
return __fix_enotdir(
CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr, 0),
path16);
if (__mkntpathat(dirfd, path, flags, path16) == -1) {
return kNtInvalidHandleValue;
}
if (GetNtOpenFlags(flags, mode, &perm, &share, &disp, &attr) == -1) {
return kNtInvalidHandleValue;
}
return __fix_enotdir(CreateFile(path16, perm, share, &kNtIsInheritable, disp,
attr | extra_attr, 0),
path16);
}
static textwindows int sys_open_nt_console(int dirfd,
@ -49,10 +56,10 @@ static textwindows int sys_open_nt_console(int dirfd,
g_fds.p[fd].handle = g_fds.p[STDIN_FILENO].handle;
g_fds.p[fd].extra = g_fds.p[STDOUT_FILENO].handle;
} else if ((g_fds.p[fd].handle = sys_open_nt_impl(
dirfd, mp->conin, (flags & ~O_ACCMODE) | O_RDONLY, mode)) !=
-1) {
g_fds.p[fd].extra = sys_open_nt_impl(dirfd, mp->conout,
(flags & ~O_ACCMODE) | O_WRONLY, mode);
dirfd, mp->conin, (flags & ~O_ACCMODE) | O_RDONLY, mode,
kNtFileFlagOverlapped)) != -1) {
g_fds.p[fd].extra = sys_open_nt_impl(
dirfd, mp->conout, (flags & ~O_ACCMODE) | O_WRONLY, mode, 0);
npassert(g_fds.p[fd].extra != -1);
} else {
return -1;
@ -66,7 +73,8 @@ static textwindows int sys_open_nt_console(int dirfd,
static textwindows int sys_open_nt_file(int dirfd, const char *file,
uint32_t flags, int32_t mode,
size_t fd) {
if ((g_fds.p[fd].handle = sys_open_nt_impl(dirfd, file, flags, mode)) != -1) {
if ((g_fds.p[fd].handle = sys_open_nt_impl(dirfd, file, flags, mode, 0)) !=
-1) {
g_fds.p[fd].kind = kFdFile;
g_fds.p[fd].flags = flags;
g_fds.p[fd].mode = mode;

View file

@ -25,51 +25,126 @@
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h"
#include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/nt/enum/filetype.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/ipc.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
static textwindows void sys_read_nt_abort(int64_t handle,
struct NtOverlapped *overlapped) {
unassert(CancelIoEx(handle, overlapped) ||
GetLastError() == kNtErrorNotFound);
}
static textwindows void MungeTerminalInput(struct Fd *fd, char *p, size_t n) {
if (!(fd->ttymagic & kFdTtyNoCr2Nl)) {
size_t i;
for (i = 0; i < n; ++i) {
if (p[i] == '\r') {
p[i] = '\n';
}
}
}
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
static textwindows void EchoTerminalInput(struct Fd *fd, char *p, size_t n) {
int64_t hOutput;
if (fd->kind == kFdConsole) {
hOutput = fd->extra;
} else {
hOutput = g_fds.p[1].handle;
}
if (fd->ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
}
}
static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
size_t size, int64_t offset) {
// try to poll rather than block
uint32_t avail;
if (GetFileType(fd->handle) == kNtFileTypePipe) {
for (;;) {
if (!PeekNamedPipe(fd->handle, 0, 0, 0, &avail, 0)) break;
if (avail) break;
POLLTRACE("sys_read_nt polling");
if (SleepEx(__SIG_POLLING_INTERVAL_MS, true) == kNtWaitIoCompletion) {
POLLTRACE("IOCP EINTR");
}
if (fd->flags & O_NONBLOCK) {
return eagain();
}
if (_check_interrupts(kSigOpRestartable, g_fds.p)) {
POLLTRACE("sys_read_nt interrupted");
return -1;
}
}
POLLTRACE("sys_read_nt ready to read");
}
// perform the read i/o operation
bool32 ok;
uint32_t got;
int filetype;
int abort_errno = EAGAIN;
size = MIN(size, 0x7ffff000);
if (offset == -1) {
// perform simple blocking read
filetype = GetFileType(fd->handle);
if (filetype == kNtFileTypeChar || filetype == kNtFileTypePipe) {
struct NtOverlapped overlap = {0};
if (offset != -1) {
// pread() and pwrite() should not be called on a pipe or tty
return espipe();
}
if ((overlap.hEvent = CreateEvent(0, 0, 0, 0))) {
// the win32 manual says it's important to *not* put &got here
// since for overlapped i/o, we always use GetOverlappedResult
ok = ReadFile(fd->handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
// i/o operation is in flight; blocking is unavoidable
// if we're in non-blocking mode, then immediately abort
// if an interrupt is pending, then abort before waiting
// otherwise wait for i/o periodically checking interrupts
if (fd->flags & O_NONBLOCK) {
sys_read_nt_abort(fd->handle, &overlap);
} else if (_check_interrupts(kSigOpRestartable, g_fds.p)) {
Interrupted:
abort_errno = errno;
sys_read_nt_abort(fd->handle, &overlap);
} else {
for (;;) {
uint32_t i;
i = WaitForSingleObject(overlap.hEvent, __SIG_POLLING_INTERVAL_MS);
if (i == kNtWaitTimeout) {
if (_check_interrupts(kSigOpRestartable, g_fds.p)) {
goto Interrupted;
}
} else {
npassert(!i);
break;
}
}
}
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(fd->handle, &overlap, &got, true);
}
CloseHandle(overlap.hEvent);
} else {
ok = false;
}
} else if (offset == -1) {
// perform simple blocking file read
ok = ReadFile(fd->handle, data, size, &got, 0);
} else {
// perform pread()-style read at particular file offset
// perform pread()-style file read at particular file offset
int64_t position;
// save file pointer which windows clobbers, even for overlapped i/o
if (!SetFilePointerEx(fd->handle, 0, &position, SEEK_CUR)) {
@ -84,6 +159,12 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
unassert(SetFilePointerEx(fd->handle, position, 0, SEEK_SET));
}
if (ok) {
if (fd->ttymagic & kFdTtyMunging) {
MungeTerminalInput(fd, data, got);
}
if (fd->ttymagic & kFdTtyEchoing) {
EchoTerminalInput(fd, data, got);
}
return got;
}
@ -94,6 +175,9 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
return 0; //
case kNtErrorAccessDenied: // read doesn't return EACCESS
return ebadf(); //
case kNtErrorOperationAborted:
errno = abort_errno;
return -1;
default:
return __winerr();
}

View file

@ -18,7 +18,7 @@
*/
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/timeval.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
@ -43,21 +43,33 @@ textwindows void _check_sigalrm(void) {
__sig_add(0, SIGALRM, SI_TIMER);
}
textwindows void sys_setitimer_nt_reset(void) {
// this function is called by fork(), because
// timers aren't inherited by forked subprocesses
bzero(&g_setitimer, sizeof(g_setitimer));
}
textwindows int sys_setitimer_nt(int which, const struct itimerval *neu,
struct itimerval *old) {
struct itimerval config;
if (which != ITIMER_REAL || (neu && (!timeval_isvalid(neu->it_value) ||
!timeval_isvalid(neu->it_interval)))) {
return einval();
}
if (neu) {
// POSIX defines setitimer() with the restrict keyword but let's
// accommodate the usage setitimer(ITIMER_REAL, &it, &it) anyway
config = *neu;
}
if (old) {
old->it_interval = g_setitimer.it_interval;
old->it_value = timeval_subz(g_setitimer.it_value, timeval_real());
}
if (neu) {
g_setitimer.it_interval = neu->it_interval;
g_setitimer.it_value = timeval_iszero(neu->it_value)
? timeval_zero
: timeval_add(timeval_real(), neu->it_value);
if (!timeval_iszero(config.it_value)) {
config.it_value = timeval_add(config.it_value, timeval_real());
}
g_setitimer = config;
}
return 0;
}

View file

@ -31,17 +31,17 @@
* Raise SIGALRM every 1.5s:
*
* sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno},
* &(struct sigaction){.sa_handler = OnSigalrm},
* NULL);
* setitimer(ITIMER_REAL,
* &(const struct itimerval){{1, 500000},
* {1, 500000}},
* NULL);
*
* Set single-shot 50ms timer callback to interrupt laggy connect():
* Single-shot alarm to interrupt connect() after 50ms:
*
* sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno,
* &(struct sigaction){.sa_handler = OnSigalrm,
* .sa_flags = SA_RESETHAND},
* NULL);
* setitimer(ITIMER_REAL,
@ -49,11 +49,14 @@
* NULL);
* if (connect(...) == -1 && errno == EINTR) { ... }
*
* Disarm timer:
* Disarm existing timer:
*
* setitimer(ITIMER_REAL, &(const struct itimerval){0}, NULL);
*
* Be sure to check for EINTR on your i/o calls, for best low latency.
* If the goal is to use alarms to interrupt blocking i/o routines, e.g.
* read(), connect(), etc. then it's important to install the signal
* handler using sigaction() rather than signal(), because the latter
* sets the `SA_RESTART` flag.
*
* Timers are not inherited across fork.
*

View file

@ -13,13 +13,19 @@ COSMOPOLITAN_C_START_
#define kFdEpoll 7
#define kFdReserved 8
#define kFdTtyEchoing 1 /* read()→write() (ECHO && !ICANON) */
#define kFdTtyEchoRaw 2 /* don't ^X visualize control codes */
#define kFdTtyMunging 4 /* enable input / output remappings */
#define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */
struct Fd {
int kind;
char kind;
bool zombie;
char ttymagic;
unsigned flags;
unsigned mode;
int64_t handle;
int64_t extra;
bool zombie;
};
struct Fds {

View file

@ -29,7 +29,11 @@ struct timeval timeval_frommillis(int64_t) pureconst;
struct timeval timeval_add(struct timeval, struct timeval) pureconst;
struct timeval timeval_sub(struct timeval, struct timeval) pureconst;
struct timeval timeval_subz(struct timeval, struct timeval) pureconst;
int64_t timeval_toseconds(struct timeval);
struct timeval timespec_totimeval(struct timespec) pureconst;
static inline struct timeval timeval_fromseconds(int64_t __x) {
return (struct timeval){__x};
}
static inline struct timespec timeval_totimespec(struct timeval __tv) {
return (struct timespec){__tv.tv_sec, __tv.tv_usec * 1000};
}

View file

@ -18,17 +18,20 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/ttydefaults.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/struct/consolescreenbufferinfoex.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/baud.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
textwindows int tcgetattr_nt(int ignored, struct termios *tio) {
int ttymagic;
int64_t in, out;
bool32 inok, outok;
uint32_t inmode, outmode;
@ -37,34 +40,43 @@ textwindows int tcgetattr_nt(int ignored, struct termios *tio) {
if (inok | outok) {
bzero(tio, sizeof(*tio));
tio->c_cflag |= CS8;
tio->c_cc[VMIN] = 1;
tio->c_cc[VINTR] = CTRL('C');
tio->c_cc[VQUIT] = CTRL('\\');
tio->c_cc[VERASE] = CTRL('?');
tio->c_cc[VWERASE] = CTRL('?'); // windows swaps these :'(
tio->c_cc[VERASE] = CTRL('H'); // windows swaps these :'(
tio->c_cc[VKILL] = CTRL('U');
tio->c_cc[VEOF] = CTRL('D');
tio->c_cc[VMIN] = CTRL('A');
tio->c_cc[VSTART] = CTRL('Q');
tio->c_cc[VSTOP] = CTRL('S');
tio->c_cc[VSUSP] = CTRL('Z');
tio->c_cc[VSTART] = _POSIX_VDISABLE;
tio->c_cc[VSTOP] = _POSIX_VDISABLE;
tio->c_cc[VSUSP] = _POSIX_VDISABLE;
tio->c_cc[VREPRINT] = CTRL('R');
tio->c_cc[VDISCARD] = CTRL('O');
tio->c_cc[VWERASE] = CTRL('W');
tio->c_cc[VLNEXT] = CTRL('V');
tio->c_iflag = IUTF8;
tio->c_lflag = ECHOE;
tio->c_cflag = CS8;
tio->_c_ispeed = B38400;
tio->_c_ospeed = B38400;
if (inok) {
ttymagic = g_fds.p[0].ttymagic;
if (inmode & kNtEnableLineInput) {
tio->c_lflag |= ICANON;
}
if (inmode & kNtEnableEchoInput) {
if ((inmode & kNtEnableEchoInput) || (ttymagic & kFdTtyEchoing)) {
tio->c_lflag |= ECHO;
}
if (!(ttymagic & kFdTtyEchoRaw)) {
tio->c_lflag |= ECHOCTL;
}
if (!(ttymagic & kFdTtyNoCr2Nl)) {
tio->c_iflag |= ICRNL;
}
if (inmode & kNtEnableProcessedInput) {
tio->c_lflag |= IEXTEN | ISIG;
if (tio->c_lflag | ECHO) {
tio->c_lflag |= ECHOE;
}
}
}

View file

@ -29,57 +29,86 @@
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
textwindows int tcsetattr_nt(int ignored, int opt, const struct termios *tio) {
int64_t in, out;
bool32 ok, inok, outok;
textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
bool32 ok;
int ttymagic;
int64_t hInput, hOutput;
uint32_t inmode, outmode;
inok = GetConsoleMode((in = __getfdhandleactual(0)), &inmode);
outok = GetConsoleMode((out = __getfdhandleactual(1)), &outmode);
if (inok | outok) {
if (__isfdkind(fd, kFdConsole)) {
// program manually opened /dev/tty in O_RDWR mode for cmd.exe
hInput = g_fds.p[fd].handle;
hOutput = g_fds.p[fd].extra;
} else if (fd == 0 || fd == 1) {
// otherwise just assume cmd.exe console stdio
// there's no serial port support yet
hInput = g_fds.p[0].handle;
hOutput = g_fds.p[1].handle;
fd = 0;
} else {
STRACE("tcsetattr(fd) must be 0, 1, or open'd /dev/tty");
return enotty();
}
if (GetConsoleMode(hInput, &inmode) && GetConsoleMode(hOutput, &outmode)) {
if (inok) {
if (opt == TCSAFLUSH) {
FlushConsoleInputBuffer(in);
if (opt == TCSAFLUSH) {
FlushConsoleInputBuffer(hInput);
}
inmode &=
~(kNtEnableLineInput | kNtEnableEchoInput | kNtEnableProcessedInput);
inmode |= kNtEnableWindowInput;
ttymagic = 0;
if (tio->c_lflag & ICANON) {
inmode |= kNtEnableLineInput;
} else {
ttymagic |= kFdTtyMunging;
if (tio->c_cc[VMIN] != 1) {
STRACE("tcsetattr c_cc[VMIN] must be 1 on Windows");
return einval();
}
inmode &=
~(kNtEnableLineInput | kNtEnableEchoInput | kNtEnableProcessedInput);
inmode |= kNtEnableWindowInput;
}
if (!(tio->c_iflag & ICRNL)) {
ttymagic |= kFdTtyNoCr2Nl;
}
if (!(tio->c_lflag & ECHOCTL)) {
ttymagic |= kFdTtyEchoRaw;
}
if (tio->c_lflag & ECHO) {
// "kNtEnableEchoInput can be used only if the
// kNtEnableLineInput mode is also enabled." -MSDN
if (tio->c_lflag & ICANON) {
inmode |= kNtEnableLineInput;
inmode |= kNtEnableEchoInput;
} else {
// If ECHO is enabled in raw mode, then read(0) needs to
// magically write(1) to simulate echoing. This normally
// visualizes control codes, e.g. \r → ^M unless ECHOCTL
// hasn't been specified.
ttymagic |= kFdTtyEchoing;
}
if (tio->c_lflag & ECHO) {
/*
* kNtEnableEchoInput can be used only if the ENABLE_LINE_INPUT mode
* is also enabled. --Quoth MSDN
*/
inmode |= kNtEnableEchoInput | kNtEnableLineInput;
}
if (tio->c_lflag & (IEXTEN | ISIG)) {
inmode |= kNtEnableProcessedInput;
}
if (IsAtLeastWindows10()) {
inmode |= kNtEnableVirtualTerminalInput;
}
ok = SetConsoleMode(in, inmode);
(void)ok;
NTTRACE("SetConsoleMode(%p, %s) → %hhhd", in,
DescribeNtConsoleInFlags(inmode), ok);
}
if (tio->c_lflag & (IEXTEN | ISIG)) {
inmode |= kNtEnableProcessedInput;
}
if (IsAtLeastWindows10()) {
inmode |= kNtEnableVirtualTerminalInput;
}
g_fds.p[fd].ttymagic = ttymagic;
ok = SetConsoleMode(hInput, inmode);
(void)ok;
NTTRACE("SetConsoleMode(%p, %s) → %hhhd", hInput,
DescribeNtConsoleInFlags(inmode), ok);
if (outok) {
outmode &= ~(kNtDisableNewlineAutoReturn);
outmode |= kNtEnableProcessedOutput;
if (!(tio->c_oflag & ONLCR)) {
outmode |= kNtDisableNewlineAutoReturn;
}
if (IsAtLeastWindows10()) {
outmode |= kNtEnableVirtualTerminalProcessing;
}
ok = SetConsoleMode(out, outmode);
(void)ok;
NTTRACE("SetConsoleMode(%p, %s) → %hhhd", out,
DescribeNtConsoleOutFlags(outmode), ok);
outmode &= ~kNtDisableNewlineAutoReturn;
outmode |= kNtEnableProcessedOutput;
if (!(tio->c_oflag & ONLCR)) {
outmode |= kNtDisableNewlineAutoReturn;
}
if (IsAtLeastWindows10()) {
outmode |= kNtEnableVirtualTerminalProcessing;
}
ok = SetConsoleMode(hOutput, outmode);
(void)ok;
NTTRACE("SetConsoleMode(%p, %s) → %hhhd", hOutput,
DescribeNtConsoleOutFlags(outmode), ok);
return 0;
} else {

View file

@ -1,7 +1,7 @@
/*-*- 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
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
@ -16,14 +16,21 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/dce.h"
#include "libc/nt/version.h"
#include "libc/calls/struct/timeval.h"
#include "libc/limits.h"
/**
* Returns true if we're running at least Windows 10.
* Converts timeval to seconds.
*
* This function may only be called if IsWindows() is true.
* This function uses ceil rounding, so 1µs becomes 1s. The addition
* operation is saturating so timeval_toseconds(timeval_max) returns
* INT64_MAX.
*/
privileged bool(IsAtLeastWindows10)(void) {
return IsAtLeastWindows10();
int64_t timeval_toseconds(struct timeval tv) {
int64_t secs;
secs = tv.tv_sec;
if (tv.tv_usec && secs < INT64_MAX) {
++secs;
}
return secs;
}

View file

@ -42,7 +42,11 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
bool32 ok;
uint32_t sent;
int64_t handle;
handle = g_fds.p[fd].handle;
if (g_fds.p[fd].kind == kFdConsole) {
handle = g_fds.p[fd].extra; // get write end of console
} else {
handle = g_fds.p[fd].handle;
}
size = MIN(size, 0x7ffff000);
if (offset == -1) {
// perform simple blocking write

View file

@ -1,38 +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 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/syscall_support-nt.internal.h"
#include "libc/intrin/describentoverlapped.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
__msabi extern typeof(CancelIoEx) *const __imp_CancelIoEx;
/**
* Cancels Windows i/o operation.
*/
bool32 CancelIoEx(int64_t hFile, struct NtOverlapped *opt_lpOverlapped) {
bool32 ok;
ok = __imp_CancelIoEx(hFile, opt_lpOverlapped);
if (!ok) __winerr();
NTTRACE("CancelIoEx(%ld, %s) → %hhhd% m", hFile,
DescribeNtOverlapped(opt_lpOverlapped), ok);
return ok;
}

View file

@ -146,7 +146,6 @@ o/$(MODE)/libc/intrin/exit1.greg.o \
o/$(MODE)/libc/intrin/wsarecv.o \
o/$(MODE)/libc/intrin/wsarecvfrom.o \
o/$(MODE)/libc/intrin/createfile.o \
o/$(MODE)/libc/intrin/cancelioex.o \
o/$(MODE)/libc/intrin/reopenfile.o \
o/$(MODE)/libc/intrin/deletefile.o \
o/$(MODE)/libc/intrin/createpipe.o \

View file

@ -29,10 +29,10 @@ __msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle;
extern uint32_t __pid_exec;
const signed char kConsoleHandles[3] = {
kNtStdInputHandle,
kNtStdOutputHandle,
kNtStdErrorHandle,
const signed char kNtConsoleHandles[3] = {
(signed char)kNtStdInputHandle,
(signed char)kNtStdOutputHandle,
(signed char)kNtStdErrorHandle,
};
// Puts cmd.exe gui back the way it was.
@ -41,7 +41,7 @@ void _restorewintty(void) {
if (!IsWindows()) return;
if (__imp_GetCurrentProcessId() != __pid_exec) return;
for (i = 0; i < 3; ++i) {
__imp_SetConsoleMode(__imp_GetStdHandle(kConsoleHandles[i]),
__imp_SetConsoleMode(__imp_GetStdHandle(kNtConsoleHandles[i]),
__ntconsolemode[i]);
}
}

View file

@ -1,7 +1,8 @@
#ifndef COSMOPOLITAN_LIBC_NT_ENUM_WAIT_H_
#define COSMOPOLITAN_LIBC_NT_ENUM_WAIT_H_
#define kNtWaitFailed 0xffffffffu
#define kNtWaitTimeout 0x00000102u
#define kNtWaitFailed 0xffffffffu
#define kNtWaitTimeout 0x00000102u
#define kNtWaitAbandoned 0x00000080u
#endif /* COSMOPOLITAN_LIBC_NT_ENUM_WAIT_H_ */

View file

@ -55,13 +55,17 @@ int64_t RegisterEventSource(const char16_t *lpUNCServerName,
const char16_t *lpSourceName);
int32_t DeregisterEventSource(uint64_t handle);
int64_t CreateEvent(struct NtSecurityAttributes *lpEventAttributes,
int64_t CreateEvent(struct NtSecurityAttributes *opt_lpEventAttributes,
bool32 bManualReset, bool32 bInitialState,
const char16_t *lpName);
const char16_t *opt_lpName);
int64_t CreateEventEx(struct NtSecurityAttributes *lpEventAttributes,
const char16_t *lpName, uint32_t dwFlags,
uint32_t dwDesiredAccess);
int32_t SetEvent(int64_t hEvent);
int32_t ResetEvent(int64_t hEvent);
int32_t PulseEvent(int64_t hEvent);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NT_EVENTS_H_ */

View file

@ -1,2 +1,18 @@
#include "libc/nt/codegen.h"
.imp kernel32,__imp_CancelIoEx,CancelIoEx
.text.windows
.ftrace1
CancelIoEx:
.ftrace2
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
mov __imp_CancelIoEx(%rip),%rax
jmp __sysv2nt
#elif defined(__aarch64__)
mov x0,#0
ret
#endif
.endfn CancelIoEx,globl
.previous

View file

@ -9,7 +9,6 @@
# KERNEL32.DLL
#
# Name Actual DLL Arity
imp '' CancelIoEx kernel32 2
imp '' CloseHandle kernel32 1
imp '' CreateDirectoryW kernel32 2
imp '' CreateFileMappingNumaW kernel32 7
@ -53,6 +52,7 @@ imp 'AttachConsole' AttachConsole kernel32 1
imp 'CallNamedPipe' CallNamedPipeW kernel32 7
imp 'CallNamedPipeA' CallNamedPipeA kernel32 7
imp 'CancelIo' CancelIo kernel32 1
imp 'CancelIoEx' CancelIoEx kernel32 2
imp 'CancelSynchronousIo' CancelSynchronousIo kernel32 1
imp 'CheckRemoteDebuggerPresent' CheckRemoteDebuggerPresent kernel32 2
imp 'ClearCommBreak' ClearCommBreak kernel32 1

View file

@ -14,9 +14,9 @@
#define kNtCpUtf8 65001
#define kNtInvalidHandleValue -1L
#define kNtStdInputHandle -10L
#define kNtStdOutputHandle -11L
#define kNtStdErrorHandle -12L
#define kNtStdInputHandle -10u
#define kNtStdOutputHandle -11u
#define kNtStdErrorHandle -12u
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -36,8 +36,8 @@ int64_t GetCurrentProcess(void) pureconst;
void ExitProcess(uint32_t uExitCode) wontreturn;
uint32_t GetLastError(void) nosideeffect;
bool32 CloseHandle(int64_t hObject) dontthrow nocallback;
intptr_t GetStdHandle(int64_t nStdHandle) nosideeffect;
bool32 SetStdHandle(int64_t nStdHandle, int64_t hHandle);
intptr_t GetStdHandle(uint32_t nStdHandle) nosideeffect;
bool32 SetStdHandle(uint32_t nStdHandle, int64_t hHandle);
bool32 SetDefaultDllDirectories(unsigned dirflags);
bool32 RtlGenRandom(void *RandomBuffer, uint32_t RandomBufferLength);
uint32_t GetModuleFileName(int64_t hModule, char16_t *lpFilename,

View file

@ -85,10 +85,6 @@ int64_t CreateSemaphore(struct NtSecurityAttributes *opt_lpSemaphoreAttributes,
uint32_t lInitialCount, uint32_t lMaximumCount,
const char16_t *opt_lpName);
int32_t SetEvent(int64_t hEvent);
int32_t ResetEvent(int64_t hEvent);
int32_t PulseEvent(int64_t hEvent);
int32_t ReleaseMutex(int64_t hMutex);
int32_t ReleaseSemaphore(int64_t hSemaphore, int32_t lReleaseCount,
int *lpPreviousCount);

View file

@ -4,7 +4,6 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
bool IsAtLeastWindows10(void) pureconst;
bool32 GetVersionEx(struct NtOsVersionInfo *lpVersionInformation);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__) && defined(__x86_64__)

View file

@ -68,6 +68,7 @@ __static_yoink("_check_sigchld");
extern int64_t __wincrashearly;
bool32 __onntconsoleevent_nt(uint32_t);
void sys_setitimer_nt_reset(void);
void kmalloc_unlock(void);
static textwindows wontreturn void AbortFork(const char *func) {
@ -396,6 +397,10 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
if (ftrace_stackdigs) {
_weaken(__hook)(_weaken(ftrace_hook), _weaken(GetSymbolTable)());
}
// reset alarms
if (_weaken(sys_setitimer_nt_reset)) {
_weaken(sys_setitimer_nt_reset)();
}
}
if (untrackpid != -1) {
__releasefd(untrackpid);

View file

@ -95,6 +95,16 @@ static dontasan void PrintDependencies(const char *prologue) {
static dontasan void Print(const char *prologue) {
}
static dontasan const char *ConvertCcToStr(int cc) {
if (cc == _POSIX_VDISABLE) {
return "_POSIX_VDISABLE";
} else {
static char buf[8] = "CTRL-";
buf[5] = CTRL(cc);
return buf;
}
}
/**
* Prints lots of information about this process, e.g.
*
@ -558,67 +568,6 @@ dontasan textstartup void __printargs(const char *prologue) {
} else if ((termios.c_cflag & CSIZE) == CS8) {
kprintf(" CS8");
}
b = cfgetospeed(&termios);
if (b == B0) {
kprintf(" B0");
} else if (b == B50) {
kprintf(" B50");
} else if (b == B75) {
kprintf(" B75");
} else if (b == B110) {
kprintf(" B110");
} else if (b == B134) {
kprintf(" B134");
} else if (b == B150) {
kprintf(" B150");
} else if (b == B200) {
kprintf(" B200");
} else if (b == B300) {
kprintf(" B300");
} else if (b == B600) {
kprintf(" B600");
} else if (b == B1200) {
kprintf(" B1200");
} else if (b == B1800) {
kprintf(" B1800");
} else if (b == B2400) {
kprintf(" B2400");
} else if (b == B4800) {
kprintf(" B4800");
} else if (b == B9600) {
kprintf(" B9600");
} else if (b == B19200) {
kprintf(" B19200");
} else if (b == B38400) {
kprintf(" B38400");
} else if (b == B57600) {
kprintf(" B57600");
} else if (b == B115200) {
kprintf(" B115200");
} else if (b == B230400) {
kprintf(" B230400");
} else if (b == B500000) {
kprintf(" B500000");
} else if (b == B576000) {
kprintf(" B576000");
} else if (b == B1000000) {
kprintf(" B1000000");
} else if (b == B1152000) {
kprintf(" B1152000");
} else if (b == B1500000) {
kprintf(" B1500000");
} else if (b == B2000000) {
kprintf(" B2000000");
} else if (b == B2500000) {
kprintf(" B2500000");
} else if (b == B3000000) {
kprintf(" B3000000");
} else if (b == B3500000) {
kprintf(" B3500000");
} else if (b == B4000000) {
kprintf(" B4000000");
}
kprintf("\n");
kprintf(prologue);
kprintf(" c_lflag =");
@ -642,21 +591,21 @@ dontasan textstartup void __printargs(const char *prologue) {
PRINT(" cfgetospeed() = %u", cfgetospeed(&termios));
PRINT(" c_cc[VMIN] = %d", termios.c_cc[VMIN]);
PRINT(" c_cc[VTIME] = %d", termios.c_cc[VTIME]);
PRINT(" c_cc[VINTR] = CTRL-%c", CTRL(termios.c_cc[VINTR]));
PRINT(" c_cc[VQUIT] = CTRL-%c", CTRL(termios.c_cc[VQUIT]));
PRINT(" c_cc[VERASE] = CTRL-%c", CTRL(termios.c_cc[VERASE]));
PRINT(" c_cc[VKILL] = CTRL-%c", CTRL(termios.c_cc[VKILL]));
PRINT(" c_cc[VEOF] = CTRL-%c", CTRL(termios.c_cc[VEOF]));
PRINT(" c_cc[VSTART] = CTRL-%c", CTRL(termios.c_cc[VSTART]));
PRINT(" c_cc[VSTOP] = CTRL-%c", CTRL(termios.c_cc[VSTOP]));
PRINT(" c_cc[VSUSP] = CTRL-%c", CTRL(termios.c_cc[VSUSP]));
PRINT(" c_cc[VEOL] = CTRL-%c", CTRL(termios.c_cc[VEOL]));
PRINT(" c_cc[VSWTC] = CTRL-%c", CTRL(termios.c_cc[VSWTC]));
PRINT(" c_cc[VREPRINT] = CTRL-%c", CTRL(termios.c_cc[VREPRINT]));
PRINT(" c_cc[VDISCARD] = CTRL-%c", CTRL(termios.c_cc[VDISCARD]));
PRINT(" c_cc[VWERASE] = CTRL-%c", CTRL(termios.c_cc[VWERASE]));
PRINT(" c_cc[VLNEXT] = CTRL-%c", CTRL(termios.c_cc[VLNEXT]));
PRINT(" c_cc[VEOL2] = CTRL-%c", CTRL(termios.c_cc[VEOL2]));
PRINT(" c_cc[VINTR] = %s", ConvertCcToStr(termios.c_cc[VINTR]));
PRINT(" c_cc[VQUIT] = %s", ConvertCcToStr(termios.c_cc[VQUIT]));
PRINT(" c_cc[VERASE] = %s", ConvertCcToStr(termios.c_cc[VERASE]));
PRINT(" c_cc[VKILL] = %s", ConvertCcToStr(termios.c_cc[VKILL]));
PRINT(" c_cc[VEOF] = %s", ConvertCcToStr(termios.c_cc[VEOF]));
PRINT(" c_cc[VSTART] = %s", ConvertCcToStr(termios.c_cc[VSTART]));
PRINT(" c_cc[VSTOP] = %s", ConvertCcToStr(termios.c_cc[VSTOP]));
PRINT(" c_cc[VSUSP] = %s", ConvertCcToStr(termios.c_cc[VSUSP]));
PRINT(" c_cc[VSWTC] = %s", ConvertCcToStr(termios.c_cc[VSWTC]));
PRINT(" c_cc[VREPRINT] = %s", ConvertCcToStr(termios.c_cc[VREPRINT]));
PRINT(" c_cc[VDISCARD] = %s", ConvertCcToStr(termios.c_cc[VDISCARD]));
PRINT(" c_cc[VWERASE] = %s", ConvertCcToStr(termios.c_cc[VWERASE]));
PRINT(" c_cc[VLNEXT] = %s", ConvertCcToStr(termios.c_cc[VLNEXT]));
PRINT(" c_cc[VEOL] = %s", ConvertCcToStr(termios.c_cc[VEOL]));
PRINT(" c_cc[VEOL2] = %s", ConvertCcToStr(termios.c_cc[VEOL2]));
} else {
PRINT(" - tcgetattr(%d) failed %m", i);
}

View file

@ -27,8 +27,13 @@
#include "libc/macros.internal.h"
#include "libc/nexgen32e/rdtsc.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/creationdisposition.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/filemapflags.h"
#include "libc/nt/enum/filesharemode.h"
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/files.h"
#include "libc/nt/memory.h"
@ -38,29 +43,37 @@
#include "libc/nt/signals.h"
#include "libc/nt/struct/ntexceptionpointers.h"
#include "libc/nt/struct/teb.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.internal.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/winargs.internal.h"
#include "libc/sock/internal.h"
#include "libc/sysv/consts/prot.h"
#ifdef __x86_64__
#if IsTiny()
// clang-format off
__msabi extern typeof(AddVectoredExceptionHandler) *const __imp_AddVectoredExceptionHandler;
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
__msabi extern typeof(CreateFile) *const __imp_CreateFileW;
__msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW;
__msabi extern typeof(DuplicateHandle) *const __imp_DuplicateHandle;
__msabi extern typeof(ExitProcess) *const __imp_ExitProcess;
__msabi extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentStringsW;
__msabi extern typeof(GetConsoleMode) *const __imp_GetConsoleMode;
__msabi extern typeof(GetCurrentProcess) *const __imp_GetCurrentProcess;
__msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId;
__msabi extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW;
__msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(SetConsoleCP) *const __imp_SetConsoleCP;
__msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode;
__msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP;
__msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle;
__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect;
#define CreateFileMapping __imp_CreateFileMappingW
#define MapViewOfFileEx __imp_MapViewOfFileEx
#define VirtualProtect __imp_VirtualProtect
#endif
#define AT_EXECFN 31L
#define MAP_ANONYMOUS 32
#define MAP_PRIVATE 2
#define PROT_EXEC 4
#define PROT_READ 1
#define PROT_WRITE 2
__msabi extern typeof(WriteFile) *const __imp_WriteFile;
// clang-format on
/*
* TODO: Why can't we allocate addresses above 4GB on Windows 7 x64?
@ -68,7 +81,7 @@ __msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect;
*/
extern int64_t __wincrashearly;
extern const signed char kConsoleHandles[3];
extern const signed char kNtConsoleHandles[3];
extern void cosmo(int, char **, char **, long (*)[2]) wontreturn;
static const short kConsoleModes[3] = {
@ -83,7 +96,7 @@ static const short kConsoleModes[3] = {
};
// https://nullprogram.com/blog/2022/02/18/
static inline char16_t *MyCommandLine(void) {
__msabi static inline char16_t *MyCommandLine(void) {
void *cmd;
asm("mov\t%%gs:(0x60),%0\n"
"mov\t0x20(%0),%0\n"
@ -92,7 +105,7 @@ static inline char16_t *MyCommandLine(void) {
return cmd;
}
static inline size_t StrLen16(const char16_t *s) {
__msabi static inline size_t StrLen16(const char16_t *s) {
size_t n;
for (n = 0;; ++n) {
if (!s[n]) {
@ -121,19 +134,45 @@ __msabi static textwindows int OnEarlyWinCrash(struct NtExceptionPointers *ep) {
p = __fixcpy(p, ep->ContextRecord ? ep->ContextRecord->Rip : -1, 32);
*p++ = '\r';
*p++ = '\n';
WriteFile(GetStdHandle(kNtStdErrorHandle), buf, p - buf, &wrote, 0);
ExitProcess(11);
__imp_WriteFile(__imp_GetStdHandle(kNtStdErrorHandle), buf, p - buf, &wrote,
0);
__imp_ExitProcess(11);
__builtin_unreachable();
}
// this makes it possible for our read() implementation to periodically
// poll for signals while performing a blocking overlapped io operation
__msabi static textwindows void ReopenConsoleForOverlappedIo(void) {
uint32_t mode;
int64_t hOld, hNew;
hOld = __imp_GetStdHandle(kNtStdInputHandle);
if (__imp_GetConsoleMode(hOld, &mode)) {
hNew = __imp_CreateFileW(u"CONIN$", kNtGenericRead | kNtGenericWrite,
kNtFileShareRead | kNtFileShareWrite,
&kNtIsInheritable, kNtOpenExisting,
kNtFileFlagOverlapped, 0);
if (hNew != kNtInvalidHandleValue) {
__imp_SetStdHandle(kNtStdInputHandle, hNew);
__imp_CloseHandle(hOld);
}
}
}
// this ensures close(1) won't accidentally close(2) for example
__msabi static textwindows void DeduplicateStdioHandles(void) {
int64_t proc, outhand, errhand, newhand;
outhand = GetStdHandle(kNtStdOutputHandle);
errhand = GetStdHandle(kNtStdErrorHandle);
if (outhand == errhand) {
proc = GetCurrentProcess();
DuplicateHandle(proc, errhand, proc, &newhand, 0, true,
kNtDuplicateSameAccess);
SetStdHandle(kNtStdErrorHandle, newhand);
long i, j;
int64_t h1, h2, h3, proc;
for (i = 0; i < 3; ++i) {
h1 = __imp_GetStdHandle(kNtConsoleHandles[i]);
for (j = i + 1; j < 3; ++j) {
h3 = h2 = __imp_GetStdHandle(kNtConsoleHandles[j]);
if (h1 == h2) {
proc = __imp_GetCurrentProcess();
__imp_DuplicateHandle(proc, h2, proc, &h3, 0, true,
kNtDuplicateSameAccess);
__imp_SetStdHandle(kNtConsoleHandles[j], h3);
}
}
}
}
@ -150,20 +189,21 @@ __msabi static textwindows wontreturn void WinMainNew(const char16_t *cmdline) {
intptr_t stackaddr, allocaddr;
version = NtGetPeb()->OSMajorVersion;
__oldstack = (intptr_t)__builtin_frame_address(0);
ReopenConsoleForOverlappedIo();
DeduplicateStdioHandles();
if ((intptr_t)v_ntsubsystem == kNtImageSubsystemWindowsCui && version >= 10) {
rc = SetConsoleCP(kNtCpUtf8);
rc = __imp_SetConsoleCP(kNtCpUtf8);
NTTRACE("SetConsoleCP(kNtCpUtf8) → %hhhd", rc);
rc = SetConsoleOutputCP(kNtCpUtf8);
rc = __imp_SetConsoleOutputCP(kNtCpUtf8);
NTTRACE("SetConsoleOutputCP(kNtCpUtf8) → %hhhd", rc);
for (i = 0; i < 3; ++i) {
hand = GetStdHandle(kConsoleHandles[i]);
rc = GetConsoleMode(hand, __ntconsolemode + i);
hand = __imp_GetStdHandle(kNtConsoleHandles[i]);
rc = __imp_GetConsoleMode(hand, __ntconsolemode + i);
NTTRACE("GetConsoleMode(%p, [%s]) → %hhhd", hand,
i ? (DescribeNtConsoleOutFlags)(outflagsbuf, __ntconsolemode[i])
: (DescribeNtConsoleInFlags)(inflagsbuf, __ntconsolemode[i]),
rc);
rc = SetConsoleMode(hand, kConsoleModes[i]);
rc = __imp_SetConsoleMode(hand, kConsoleModes[i]);
NTTRACE("SetConsoleMode(%p, %s) → %hhhd", hand,
i ? (DescribeNtConsoleOutFlags)(outflagsbuf, kConsoleModes[i])
: (DescribeNtConsoleInFlags)(inflagsbuf, kConsoleModes[i]),
@ -179,14 +219,15 @@ __msabi static textwindows wontreturn void WinMainNew(const char16_t *cmdline) {
allocaddr = stackaddr;
allocsize = stacksize + sizeof(struct WinArgs);
NTTRACE("WinMainNew() mapping %'zu byte stack at %p", allocsize, allocaddr);
MapViewOfFileEx(
(_mmi.p[0].h =
CreateFileMapping(-1, &kNtIsInheritable, kNtPageExecuteReadwrite,
__imp_MapViewOfFileEx((_mmi.p[0].h = __imp_CreateFileMappingW(
-1, &kNtIsInheritable, kNtPageExecuteReadwrite,
allocsize >> 32, allocsize, NULL)),
kNtFileMapWrite | kNtFileMapExecute, 0, 0, allocsize, (void *)allocaddr);
kNtFileMapWrite | kNtFileMapExecute, 0, 0, allocsize,
(void *)allocaddr);
prot = (intptr_t)ape_stack_prot;
if (~prot & PROT_EXEC) {
VirtualProtect((void *)allocaddr, allocsize, kNtPageReadwrite, &oldprot);
__imp_VirtualProtect((void *)allocaddr, allocsize, kNtPageReadwrite,
&oldprot);
}
_mmi.p[0].x = allocaddr >> 16;
_mmi.p[0].y = (allocaddr >> 16) + ((allocsize - 1) >> 16);
@ -203,57 +244,26 @@ __msabi static textwindows wontreturn void WinMainNew(const char16_t *cmdline) {
wa->argv[0][i] = '/';
}
}
env16 = GetEnvironmentStrings();
env16 = __imp_GetEnvironmentStringsW();
NTTRACE("WinMainNew() loading environment");
GetDosEnviron(env16, wa->envblock, ARRAYLEN(wa->envblock) - 8, wa->envp,
ARRAYLEN(wa->envp) - 1);
FreeEnvironmentStrings(env16);
__imp_FreeEnvironmentStringsW(env16);
NTTRACE("WinMainNew() switching stacks");
_jmpstack((char *)(stackaddr + stacksize - (intptr_t)ape_stack_align), cosmo,
count, wa->argv, wa->envp, wa->auxv);
}
/**
* Main function on Windows NT.
*
* The Cosmopolitan Runtime provides the following services, which aim
* to bring Windows NT behavior closer in harmony with System Five:
*
* 1. We configure CMD.EXE for UTF-8 and enable ANSI colors on Win10.
*
* 2. Command line arguments are passed as a blob of UTF-16 text. We
* chop them up into an char *argv[] UTF-8 data structure, in
* accordance with the DOS conventions for argument quoting.
*
* 3. Environment variables are passed to us as a sorted UTF-16 double
* NUL terminated list. We translate this to char ** using UTF-8.
*
* 4. Allocates new stack at a high address. NT likes to choose a
* stack address that's beneath the program image. We want to be
* able to assume that stack addresses are located at higher
* addresses than heap and program memory.
*
* 5. Reconfigure x87 FPU so long double is actually long (80 bits).
*
* 6. Finally, we need fork. Since disagreeing with fork is axiomatic to
* Microsoft's engineering culture, we need to go to great lengths to
* have it anyway without breaking Microsoft's rules: using the WIN32
* API (i.e. not NTDLL) to copy MAP_PRIVATE pages via a pipe. It'd go
* faster if the COW pages CreateFileMappingNuma claims to have turns
* out to be true. Until then we have a "PC Scale" and entirely legal
* workaround that they hopefully won't block using Windows Defender.
*
* @param hInstance call GetModuleHandle(NULL) from main if you need it
*/
__msabi textwindows int64_t WinMain(int64_t hInstance, int64_t hPrevInstance,
const char *lpCmdLine, int64_t nCmdShow) {
const char16_t *cmdline;
extern char os asm("__hostos");
os = _HOSTWINDOWS; /* madness https://news.ycombinator.com/item?id=21019722 */
kStartTsc = rdtsc();
__pid = GetCurrentProcessId();
__pid = __imp_GetCurrentProcessId();
#if !IsTiny()
__wincrashearly = AddVectoredExceptionHandler(1, (void *)OnEarlyWinCrash);
__wincrashearly =
__imp_AddVectoredExceptionHandler(1, (void *)OnEarlyWinCrash);
#endif
cmdline = MyCommandLine();
#ifdef SYSDEBUG
@ -261,8 +271,12 @@ __msabi textwindows int64_t WinMain(int64_t hInstance, int64_t hPrevInstance,
if (__strstr16(cmdline, u"--strace")) ++__strace;
#endif
NTTRACE("WinMain()");
if (_weaken(WinSockInit)) _weaken(WinSockInit)();
if (_weaken(WinMainForked)) _weaken(WinMainForked)();
if (_weaken(WinSockInit)) {
_weaken(WinSockInit)();
}
if (_weaken(WinMainForked)) {
_weaken(WinMainForked)();
}
WinMainNew(cmdline);
}

View file

@ -21,11 +21,11 @@
#include "libc/calls/sig.internal.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/enum/wsa.h"
#include "libc/nt/errors.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
@ -36,50 +36,61 @@
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
static textwindows void __wsablock_abort(int64_t handle,
struct NtOverlapped *overlapped) {
unassert(CancelIoEx(handle, overlapped) ||
GetLastError() == kNtErrorNotFound);
}
textwindows int __wsablock(struct Fd *fd, struct NtOverlapped *overlapped,
uint32_t *flags, int sigops, uint32_t timeout) {
int e, rc;
uint32_t i, got;
int rc, abort_errno;
if (WSAGetLastError() != kNtErrorIoPending) {
NTTRACE("sock i/o failed %s", strerror(errno));
// our i/o operation never happened because it failed
return __winsockerr();
}
// our i/o operation is in flight and it needs to block
abort_errno = EAGAIN;
if (fd->flags & O_NONBLOCK) {
e = errno;
unassert(CancelIoEx(fd->handle, overlapped) ||
WSAGetLastError() == kNtErrorNotFound);
errno = e;
__wsablock_abort(fd->handle, overlapped);
} else if (_check_interrupts(sigops, g_fds.p)) {
Interrupted:
abort_errno = errno; // EINTR or ECANCELED
__wsablock_abort(fd->handle, overlapped);
} else {
if (_check_interrupts(sigops, g_fds.p)) {
return -1;
}
}
for (;;) {
i = WSAWaitForMultipleEvents(1, &overlapped->hEvent, true,
__SIG_POLLING_INTERVAL_MS, true);
if (i == kNtWaitFailed) {
NTTRACE("WSAWaitForMultipleEvents failed %lm");
return __winsockerr();
} else if (i == kNtWaitTimeout || i == kNtWaitIoCompletion) {
if (_check_interrupts(sigops, g_fds.p)) {
return -1;
}
if (timeout) {
if (timeout <= __SIG_POLLING_INTERVAL_MS) {
NTTRACE("__wsablock timeout elapsed");
return eagain();
for (;;) {
i = WSAWaitForMultipleEvents(1, &overlapped->hEvent, true,
__SIG_POLLING_INTERVAL_MS, true);
if (i == kNtWaitFailed || i == kNtWaitTimeout) {
if (_check_interrupts(sigops, g_fds.p)) {
goto Interrupted;
}
timeout -= __SIG_POLLING_INTERVAL_MS;
if (i == kNtWaitFailed) {
// Failure should be an impossible condition, but MSDN lists
// WSAENETDOWN and WSA_NOT_ENOUGH_MEMORY as possible errors,
// which we're going to hope are ephemeral.
SleepEx(__SIG_POLLING_INTERVAL_MS, false);
}
if (timeout) {
if (timeout <= __SIG_POLLING_INTERVAL_MS) {
__wsablock_abort(fd->handle, overlapped);
break;
}
timeout -= __SIG_POLLING_INTERVAL_MS;
}
} else {
break;
}
} else {
break;
}
}
if (WSAGetOverlappedResult(fd->handle, overlapped, &got, false, flags)) {
// overlapped is allocated on stack by caller, so it's important that
// we wait for win32 to acknowledge that it's done using that memory.
if (WSAGetOverlappedResult(fd->handle, overlapped, &got, true, flags)) {
return got;
} else if ((fd->flags & O_NONBLOCK) &&
WSAGetLastError() == kNtErrorOperationAborted) {
return eagain();
} else if (WSAGetLastError() == kNtErrorOperationAborted) {
errno = abort_errno;
return -1;
} else {
return -1;
}

View file

@ -30,6 +30,9 @@
* The returned memory is owned by the stream. It'll be reused when
* fgetln() is called again. It's free()'d upon fclose() / fflush()
*
* When reading from the console on Windows in `ICANON` mode, the
* returned line will end with `\r\n` rather than `\n`.
*
* @param stream specifies non-null open input stream
* @param len optionally receives byte length of line
* @return nul-terminated line string, including the `\n` character

View file

@ -26,6 +26,9 @@
* exceeding size. The line ending marker is included and may be removed
* using _chomp().
*
* When reading from the console on Windows in `ICANON` mode, the
* returned line will end with `\r\n` rather than `\n`.
*
* @param s is output buffer
* @param size is capacity of s
* @param f is non-null file object stream pointer

View file

@ -33,13 +33,16 @@
* documentation. Concerning lines, please note the \n or \r\n are
* included in results, and can be removed with _chomp().
*
* @param line is the caller's buffer (in/out) which is extended
* automatically. *line may be NULL but only if *n is 0;
* When reading from the console on Windows in `ICANON` mode, the
* returned line will end with `\r\n` rather than `\n`.
*
* @param linebuf is the caller's buffer (in/out) which is extended
* automatically. *line may be NULL but only if *capacity is 0;
* NUL-termination is guaranteed FTMP
* @return number of bytes read, including delim, excluding NUL, or -1
* w/ errno on EOF or error; see ferror() and feof()
* @see fgetln(), xgetline(), getdelim(), gettok_r()
*/
ssize_t getline(char **line, size_t *n, FILE *f) {
return getdelim(line, n, '\n', f);
ssize_t getline(char **linebuf, size_t *capacity, FILE *f) {
return getdelim(linebuf, capacity, '\n', f);
}

View file

@ -18,14 +18,18 @@
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/sock/internal.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
@ -43,10 +47,35 @@ TEST(read, eof) {
ASSERT_SYS(0, 0, close(3));
}
volatile bool got_sigalrm;
void OnSigalrm(int sig) {
got_sigalrm = true;
}
TEST(read_pipe, canBeInterruptedByAlarm) {
int fds[2];
char buf[1];
struct sigaction sa;
alarm(1);
sa.sa_flags = 0;
sa.sa_handler = OnSigalrm;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, 0);
ASSERT_SYS(0, 0, pipe(fds));
ASSERT_SYS(ESPIPE, -1, pread(fds[0], buf, 1, 777));
ASSERT_SYS(EINTR, -1, read(fds[0], buf, 1));
ASSERT_TRUE(got_sigalrm);
signal(SIGALRM, SIG_DFL);
close(fds[1]);
close(fds[0]);
}
////////////////////////////////////////////////////////////////////////////////
BENCH(read, bench) {
char buf[16];
BEGIN_CANCELLATION_POINT;
ASSERT_SYS(0, 3, open("/dev/zero", O_RDONLY));
EZBENCH2("read", donothing, read(3, buf, 5));
EZBENCH2("pread", donothing, pread(3, buf, 5, 0));
@ -59,4 +88,5 @@ BENCH(read, bench) {
EZBENCH2("sys_read", donothing, sys_read(3, buf, 5));
EZBENCH2("sys_readv", donothing, sys_readv(3, &(struct iovec){buf, 5}, 1));
ASSERT_SYS(0, 0, close(3));
END_CANCELLATION_POINT;
}

View file

@ -21,18 +21,21 @@
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/ucontext.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/time/time.h"
bool gotsig;
void SetUpOnce(void) {
ASSERT_SYS(0, 0, pledge("stdio", 0));
ASSERT_SYS(0, 0, pledge("stdio proc", 0));
}
void OnSigAlrm(int sig, siginfo_t *si, void *ctx) {
@ -59,3 +62,15 @@ TEST(setitimer, testSingleShot) {
EXPECT_EQ(0, sigaction(SIGUSR1, &oldalrm, 0));
EXPECT_EQ(true, gotsig);
}
TEST(setitimer, notInheritedAcrossFork) {
struct itimerval disarm = {0};
struct itimerval singleshot = {{0}, {100}};
ASSERT_SYS(0, 0, setitimer(ITIMER_REAL, &singleshot, 0));
SPAWN(fork);
struct itimerval it;
ASSERT_SYS(0, 0, setitimer(ITIMER_REAL, 0, &it));
ASSERT_TRUE(timeval_iszero(it.it_value));
EXITS(0);
ASSERT_SYS(0, 0, setitimer(ITIMER_REAL, &disarm, 0));
}

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
#include "libc/intrin/kprintf.h"
#include "libc/limits.h"
#include "libc/stdio/rand.h"
@ -84,6 +85,15 @@ TEST(timespec_tonanos, test) {
EXPECT_EQ(INT64_MIN, timespec_tonanos((struct timespec){INT64_MIN, 0}));
}
TEST(timeval_toseconds, test) {
ASSERT_EQ(0, timeval_toseconds((struct timeval){0, 0}));
ASSERT_EQ(1, timeval_toseconds((struct timeval){0, 1}));
ASSERT_EQ(1, timeval_toseconds((struct timeval){0, 2}));
ASSERT_EQ(1, timeval_toseconds((struct timeval){1, 0}));
ASSERT_EQ(2, timeval_toseconds((struct timeval){1, 1}));
ASSERT_EQ(INT64_MAX, timeval_toseconds(timeval_max));
}
static long mod(long x, long y) {
if (y == -1) return 0;
return x - y * (x / y - (x % y && (x ^ y) < 0));

View file

@ -448,7 +448,7 @@ int SpawnSubprocesses(int argc, char *argv[]) {
break;
}
}
CHECK_NE(-1, unlink(tpath));
unlink(tpath);
sigprocmask(SIG_SETMASK, &savemask, 0);
sigaction(SIGQUIT, &savequit, 0);
sigaction(SIGINT, &saveint, 0);

View file

@ -474,12 +474,25 @@ void HandleClient(void) {
CHECK_NE(-1, events); // EINTR shouldn't be possible
if (events) {
if (fds[0].revents) {
if (!(fds[0].revents & POLLHUP)) {
WARNF("%s got unexpected input event from client %#x", exename,
fds[0].revents);
int received;
char buf[512];
INFOF("mbedtls_ssl_read");
received = mbedtls_ssl_read(&ezssl, buf, sizeof(buf));
if (!received) {
WARNF("%s client disconnected so killing worker %d", exename, child);
goto TerminateJob;
}
WARNF("%s client disconnected so killing worker %d", exename, child);
goto TerminateJob;
if (received > 0) {
WARNF("%s client sent %d unexpected bytes so killing job", exename,
received);
goto TerminateJob;
}
if (received != MBEDTLS_ERR_SSL_WANT_READ) {
WARNF("%s client ssl read failed with -0x%04x so killing job",
exename, -received);
goto TerminateJob;
}
INFOF("got spurious ssl data");
}
if (fds[1].revents) {
INFOF("read");