7
7
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8
8
╚─────────────────────────────────────────────────────────────────*/
9
9
#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
+ //
24
47
25
48
volatile bool gotsig ;
26
49
@@ -34,23 +57,41 @@ void SignalHandler(int sig) {
34
57
gotsig = true;
35
58
}
36
59
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
+
37
65
int main (int argc , char * argv []) {
38
66
39
- printf ("echoing stdin until ctrl+c is pressed\n" );
67
+ WRITE ("echoing stdin until ctrl+c is pressed\n" );
40
68
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
44
83
sigaction (SIGINT , & (struct sigaction ){.sa_handler = SignalHandler }, 0 );
45
84
46
85
for (;;) {
47
86
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" );
51
92
for (volatile int i = 0 ; i < INT_MAX / 5 ; ++ i ) {
52
93
if (gotsig ) {
53
- printf ("\rgot ctrl+c asynchronously\n" );
94
+ WRITE ("\rgot ctrl+c asynchronously\n" );
54
95
exit (0 );
55
96
}
56
97
}
@@ -71,14 +112,18 @@ int main(int argc, char *argv[]) {
71
112
72
113
// read data from standard input
73
114
//
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" );
82
127
int got = read (0 , buf , sizeof (buf ));
83
128
84
129
// check if the read operation failed
@@ -94,10 +139,10 @@ int main(int argc, char *argv[]) {
94
139
// the \r character is needed so when the line is printed
95
140
// it'll overwrite the ^C that got echo'd with the ctrl-c
96
141
if (gotsig ) {
97
- printf ("\rgot ctrl+c via i/o eintr\n" );
142
+ WRITE ("\rgot ctrl+c via i/o eintr\n" );
98
143
exit (0 );
99
144
} else {
100
- printf ("\rgot spurious eintr\n" );
145
+ WRITE ("\rgot spurious eintr\n" );
101
146
continue ;
102
147
}
103
148
} else {
@@ -109,16 +154,34 @@ int main(int argc, char *argv[]) {
109
154
110
155
// check if the user typed ctrl-d which closes the input handle
111
156
if (!got ) {
112
- printf ("got eof\n" );
157
+ WRITE ("got eof\n" );
113
158
exit (0 );
114
159
}
115
160
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.
117
165
//
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
121
170
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" );
123
186
}
124
187
}
0 commit comments