Skip to content

Commit 03875be

Browse files
committed
Add missing ICANON features
1 parent dd8544c commit 03875be

22 files changed

+526
-251
lines changed

examples/ctrlc.c

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,43 @@
77
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
88
╚─────────────────────────────────────────────────────────────────*/
99
#endif
10-
#include "libc/assert.h"
11-
#include "libc/calls/calls.h"
12-
#include "libc/calls/struct/sigaction.h"
13-
#include "libc/errno.h"
14-
#include "libc/limits.h"
15-
#include "libc/runtime/runtime.h"
16-
#include "libc/sock/struct/pollfd.h"
17-
#include "libc/stdio/stdio.h"
18-
#include "libc/str/str.h"
19-
#include "libc/sysv/consts/f.h"
20-
#include "libc/sysv/consts/limits.h"
21-
#include "libc/sysv/consts/o.h"
22-
#include "libc/sysv/consts/poll.h"
23-
#include "libc/sysv/consts/sig.h"
10+
#include <ctype.h>
11+
#include <errno.h>
12+
#include <limits.h>
13+
#include <signal.h>
14+
#include <stdio.h>
15+
#include <string.h>
16+
#include <termios.h>
17+
#include <unistd.h>
18+
19+
// this program is used by jart for manually testing teletype interrupts
20+
// and canonical mode line editing. this file documents the hidden depth
21+
// of 1960's era computer usage, that's entrenched in primitive i/o apis
22+
//
23+
// manual testing checklist:
24+
//
25+
// - "hello" enter echos "got: hello^J"
26+
//
27+
// - "hello" ctrl-d echos "got: hello"
28+
//
29+
// - "hello" ctrl-r echos "^R\nhello"
30+
//
31+
// - "hello" ctrl-u enter echos "got: ^J"
32+
//
33+
// - ctrl-d during i/o task prints "got eof" and exits
34+
//
35+
// - ctrl-d during cpu task gets delayed until read() is called
36+
//
37+
// - ctrl-c during cpu task echos ^C, then calls SignalHandler()
38+
// asynchronously, and program exits
39+
//
40+
// - ctrl-c during i/o task echos ^C, then calls SignalHandler()
41+
// asynchronously, read() raises EINTR, and program exits
42+
//
43+
// - ctrl-v ctrl-c should echo "^\b" then echo "^C" and insert "\3"
44+
//
45+
// - ctrl-v ctrl-d should echo "^\b" then echo "^D" and insert "\4"
46+
//
2447

2548
volatile bool gotsig;
2649

@@ -34,23 +57,41 @@ void SignalHandler(int sig) {
3457
gotsig = true;
3558
}
3659

60+
// this is the easiest way to write a string literal to standard output,
61+
// without formatting. printf() has an enormous binary footprint so it's
62+
// nice to avoid linking that when it is not needed.
63+
#define WRITE(sliteral) write(1, sliteral, sizeof(sliteral) - 1)
64+
3765
int main(int argc, char *argv[]) {
3866

39-
printf("echoing stdin until ctrl+c is pressed\n");
67+
WRITE("echoing stdin until ctrl+c is pressed\n");
4068

41-
// you need to set your signal handler using sigaction() rather than
42-
// signal(), since the latter uses .sa_flags=SA_RESTART, which means
43-
// read will restart itself after signals, rather than raising EINTR
69+
// when you type ctrl-c, by default it'll kill the process, unless you
70+
// define a SIGINT handler. there's multiple ways to do it. the common
71+
// way is to say signal(SIGINT, func) which is normally defined to put
72+
// the signal handler in Berkeley-style SA_RESTART mode. that means if
73+
// a signal handler is called while inside a function like read() then
74+
// the read operation will keep going afterwards like nothing happened
75+
// which can make it difficult to break your event loop. to avoid this
76+
// we can use sigaction() without specifying SA_RESTART in sa_flag and
77+
// that'll put the signal in system v mode. this means that whenever a
78+
// signal handler function in your program is called during an i/o op,
79+
// that i/o op will return an EINTR error, so you can churn your loop.
80+
// don't take that error too seriously though since SIGINT can also be
81+
// delivered asynchronously, during the times you're crunching numbers
82+
// rather than performing i/o which means you get no EINTR to warn you
4483
sigaction(SIGINT, &(struct sigaction){.sa_handler = SignalHandler}, 0);
4584

4685
for (;;) {
4786

48-
// some programs are blocked on cpu rather than i/o
49-
// such programs shall rely on asynchronous signals
50-
printf("doing cpu task...\n");
87+
// asynchronous signals are needed to interrupt math, which we shall
88+
// simulate here. signals can happen any time any place. that's only
89+
// not the case when you use sigprocmask() to block signals which is
90+
// useful for kicking the can down the road.
91+
WRITE("doing cpu task...\n");
5192
for (volatile int i = 0; i < INT_MAX / 5; ++i) {
5293
if (gotsig) {
53-
printf("\rgot ctrl+c asynchronously\n");
94+
WRITE("\rgot ctrl+c asynchronously\n");
5495
exit(0);
5596
}
5697
}
@@ -71,14 +112,18 @@ int main(int argc, char *argv[]) {
71112

72113
// read data from standard input
73114
//
74-
// since this is a blocking operation and we're not performing a
75-
// cpu-bound operation it is almost with absolute certainty that
76-
// when the ctrl-c signal gets delivered, it'll happen in read()
77-
//
78-
// it's possible to be more precise if we were building library
79-
// code. for example, you can block signals using sigprocmask()
80-
// and then use pselect() to do the waiting.
81-
printf("doing read i/o task...\n");
115+
// assuming you started this program in your terminal standard input
116+
// will be plugged into your termios driver, which cosmpolitan codes
117+
// in libc/calls/read-nt.c on windows. your read() function includes
118+
// a primitive version of readline/linenoise called "canonical mode"
119+
// which lets you edit the data that'll be returned by read() before
120+
// it's actually returned. for example, if you type hello and enter,
121+
// then "hello\n" will be returned. if you type hello and then ^D or
122+
// ctrl-d, then "hello" will be returned. the ctrl-d keystroke is in
123+
// fact an ascii control code whose special behavior can be bypassed
124+
// if you type ctrl-v ctrl-d and then enter, in which case "\3\n" is
125+
// returned, also known as ^D^J.
126+
WRITE("doing read i/o task...\n");
82127
int got = read(0, buf, sizeof(buf));
83128

84129
// check if the read operation failed
@@ -94,10 +139,10 @@ int main(int argc, char *argv[]) {
94139
// the \r character is needed so when the line is printed
95140
// it'll overwrite the ^C that got echo'd with the ctrl-c
96141
if (gotsig) {
97-
printf("\rgot ctrl+c via i/o eintr\n");
142+
WRITE("\rgot ctrl+c via i/o eintr\n");
98143
exit(0);
99144
} else {
100-
printf("\rgot spurious eintr\n");
145+
WRITE("\rgot spurious eintr\n");
101146
continue;
102147
}
103148
} else {
@@ -109,16 +154,34 @@ int main(int argc, char *argv[]) {
109154

110155
// check if the user typed ctrl-d which closes the input handle
111156
if (!got) {
112-
printf("got eof\n");
157+
WRITE("got eof\n");
113158
exit(0);
114159
}
115160

116-
// relay read data to standard output
161+
// visualize line data returned by canonical mode to standard output
162+
//
163+
// it's usually safe to ignore the return code of write; your system
164+
// will send SIGPIPE if there's any problem, which kills by default.
117165
//
118-
// it's usually safe to ignore the return code of write. the
119-
// operating system will send SIGPIPE if there's any problem
120-
// which kills the process by default
166+
// it's possible to use keyboard shortcuts to embed control codes in
167+
// the line. so we visualize them using the classic tty notation. it
168+
// is also possible to type the ascii representation, so we use bold
169+
// to visually distinguish ascii codes. see also o//examples/ttyinfo
121170
write(1, "got: ", 5);
122-
write(1, buf, got);
171+
for (int i = 0; i < got; ++i) {
172+
if (isascii(buf[i])) {
173+
if (iscntrl(buf[i])) {
174+
char ctl[2];
175+
ctl[0] = '^';
176+
ctl[1] = buf[i] ^ 0100;
177+
WRITE("\033[1m");
178+
write(1, ctl, 2);
179+
WRITE("\033[0m");
180+
} else {
181+
write(1, &buf[i], 1);
182+
}
183+
}
184+
}
185+
WRITE("\n");
123186
}
124187
}

libc/calls/internal.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#ifndef COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_
22
#define COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_
33
#include "libc/atomic.h"
4-
#include "libc/intrin/fds.h"
4+
#include "libc/calls/struct/sigset.h"
55
#include "libc/calls/struct/sigval.h"
66
#include "libc/dce.h"
7+
#include "libc/intrin/fds.h"
78
#include "libc/macros.h"
89
#include "libc/stdbool.h"
910

@@ -25,6 +26,7 @@ uint32_t sys_getuid_nt(void);
2526
int __ensurefds_unlocked(int);
2627
void __printfds(struct Fd *, size_t);
2728
int CountConsoleInputBytes(void);
29+
int CountConsoleInputBytesBlocking(uint32_t, sigset_t);
2830
int FlushConsoleInputBytes(void);
2931
int64_t GetConsoleInputHandle(void);
3032
int64_t GetConsoleOutputHandle(void);

libc/calls/linkat.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*
3535
* @param flags can have AT_EMPTY_PATH or AT_SYMLINK_NOFOLLOW
3636
* @return 0 on success, or -1 w/ errno
37+
* @raise EROFS if either path is under /zip/...
3738
* @asyncsignalsafe
3839
*/
3940
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath,
@@ -42,7 +43,7 @@ int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath,
4243
if (_weaken(__zipos_notat) &&
4344
((rc = __zipos_notat(olddirfd, oldpath)) == -1 ||
4445
(rc = __zipos_notat(newdirfd, newpath)) == -1)) {
45-
STRACE("zipos fchownat not supported yet");
46+
rc = erofs();
4647
} else if (!IsWindows()) {
4748
rc = sys_linkat(olddirfd, oldpath, newdirfd, newpath, flags);
4849
} else {

libc/calls/mkdirat.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
int mkdirat(int dirfd, const char *path, unsigned mode) {
5454
int rc;
5555
if (_weaken(__zipos_notat) && (rc = __zipos_notat(dirfd, path)) == -1) {
56-
STRACE("zipos mkdirat not supported yet");
56+
rc = erofs();
5757
} else if (!IsWindows()) {
5858
rc = sys_mkdirat(dirfd, path, mode);
5959
} else {

libc/calls/park.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
3434
int sig, handler_was_called;
3535
if (_check_cancel() == -1)
3636
return -1;
37-
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
37+
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))
3838
goto HandleSignal;
39-
}
4039
int expect = 0;
4140
atomic_int futex = 0;
4241
struct PosixThread *pt = _pthread_self();
@@ -49,9 +48,11 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
4948
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
5049
if (_check_cancel() == -1)
5150
return -1;
52-
if (!restartable || (handler_was_called & SIG_HANDLED_NO_RESTART)) {
51+
if (handler_was_called & SIG_HANDLED_NO_RESTART)
5352
return eintr();
54-
}
53+
if (handler_was_called & SIG_HANDLED_SA_RESTART)
54+
if (!restartable)
55+
return eintr();
5556
}
5657
return 0;
5758
}

libc/calls/poll-nt.c

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
7979
bool ok;
8080
uint64_t millis;
8181
uint32_t cm, avail, waitfor;
82-
struct sys_pollfd_nt pipefds[8];
82+
struct sys_pollfd_nt pipefds[64];
8383
struct sys_pollfd_nt sockfds[64];
8484
int pipeindices[ARRAYLEN(pipefds)];
8585
int sockindices[ARRAYLEN(sockfds)];
86-
struct timespec started, deadline, remain, now;
86+
struct timespec deadline, remain, now;
8787
int i, rc, sn, pn, gotinvals, gotpipes, gotsocks;
8888

89-
started = timespec_real();
90-
deadline = timespec_add(started, timespec_frommillis(ms ? *ms : -1u));
89+
waitfor = ms ? *ms : -1u;
90+
deadline = timespec_add(timespec_mono(), timespec_frommillis(waitfor));
9191

9292
// do the planning
9393
// we need to read static variables
@@ -168,16 +168,39 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
168168
pipefds[i].revents |= POLLERR_;
169169
}
170170
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
171-
switch (CountConsoleInputBytes()) {
172-
case 0:
173-
break;
174-
case -1:
175-
pipefds[i].revents &= ~POLLWRNORM_;
176-
pipefds[i].revents |= POLLHUP_;
177-
break;
178-
default:
179-
pipefds[i].revents |= POLLRDNORM_;
180-
break;
171+
// some programs like bash like to poll([stdin], 1, -1) so let's
172+
// avoid busy looping in such cases. we could generalize this to
173+
// always avoid busy loops, but we'd need poll to launch threads
174+
if (pn == 1 && sn == 0 && (pipefds[i].events & POLLRDNORM_)) {
175+
int err = errno;
176+
switch (CountConsoleInputBytesBlocking(waitfor, sigmask)) {
177+
case -1:
178+
if (errno == EINTR || errno == ECANCELED)
179+
return -1;
180+
errno = err;
181+
pipefds[i].revents &= ~POLLWRNORM_;
182+
pipefds[i].revents |= POLLERR_;
183+
break;
184+
case 0:
185+
pipefds[i].revents &= ~POLLWRNORM_;
186+
pipefds[i].revents |= POLLHUP_;
187+
break;
188+
default:
189+
pipefds[i].revents |= POLLRDNORM_;
190+
break;
191+
}
192+
} else {
193+
switch (CountConsoleInputBytes()) {
194+
case 0:
195+
break;
196+
case -1:
197+
pipefds[i].revents &= ~POLLWRNORM_;
198+
pipefds[i].revents |= POLLHUP_;
199+
break;
200+
default:
201+
pipefds[i].revents |= POLLRDNORM_;
202+
break;
203+
}
181204
}
182205
} else {
183206
// we have no way of polling if a non-socket is readable yet
@@ -202,7 +225,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
202225
// check for pending signals, thread cancelation, etc.
203226
waitfor = 0;
204227
if (!gotinvals && !gotsocks && !gotpipes) {
205-
now = timespec_real();
228+
now = timespec_mono();
206229
if (timespec_cmp(now, deadline) < 0) {
207230
remain = timespec_sub(deadline, now);
208231
millis = timespec_tomillis(remain);
@@ -211,7 +234,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
211234
if (waitfor) {
212235
POLLTRACE("poll() sleeping for %'d out of %'lu ms", waitfor,
213236
timespec_tomillis(remain));
214-
if ((rc = _park_norestart(waitfor, sigmask)) == -1)
237+
if (_park_norestart(waitfor, sigmask) == -1)
215238
return -1; // eintr, ecanceled, etc.
216239
}
217240
}

0 commit comments

Comments
 (0)