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

@ -0,0 +1,36 @@
/*-*- 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/struct/timeval.h"
#include "libc/limits.h"
/**
* Converts timeval to seconds.
*
* This function uses ceil rounding, so 1µs becomes 1s. The addition
* operation is saturating so timeval_toseconds(timeval_max) returns
* INT64_MAX.
*/
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