mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-24 19:40:28 +00:00
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:
parent
decf216655
commit
33d280c8ba
34 changed files with 580 additions and 376 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
36
libc/calls/timeval_toseconds.c
Normal file
36
libc/calls/timeval_toseconds.c
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue