]> git.saurik.com Git - apple/xnu.git/blob - tests/kevent_pty.c
xnu-6153.11.26.tar.gz
[apple/xnu.git] / tests / kevent_pty.c
1 #ifdef T_NAMESPACE
2 #undef T_NAMESPACE
3 #endif /* T_NAMESPACE */
4
5 #include <Block.h>
6 #include <darwintest.h>
7 #include <dispatch/dispatch.h>
8 #include <err.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <signal.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <stdint.h>
15 #include <unistd.h>
16 #include <util.h>
17
18 T_GLOBAL_META(
19 T_META_NAMESPACE("xnu.kevent"),
20 T_META_CHECK_LEAKS(false),
21 T_META_RUN_CONCURRENTLY(true));
22
23 #define TIMEOUT_SECS 10
24
25 static int child_ready[2];
26
27 static void
28 child_tty_client(void)
29 {
30 dispatch_source_t src;
31 char buf[16] = "";
32 ssize_t bytes_wr;
33
34 src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
35 (uintptr_t)STDIN_FILENO, 0, NULL);
36 if (!src) {
37 exit(1);
38 }
39 dispatch_source_set_event_handler(src, ^{});
40
41 dispatch_activate(src);
42
43 close(child_ready[0]);
44 snprintf(buf, sizeof(buf), "%ds", getpid());
45 bytes_wr = write(child_ready[1], buf, strlen(buf));
46 if (bytes_wr < 0) {
47 err(1, "failed to write on child ready pipe");
48 }
49
50 dispatch_main();
51 }
52
53 static void
54 pty_master(void)
55 {
56 pid_t child_pid;
57 int ret;
58
59 child_pid = fork();
60 if (child_pid == 0) {
61 child_tty_client();
62 }
63 ret = setpgid(child_pid, child_pid);
64 if (ret < 0) {
65 exit(1);
66 }
67 ret = tcsetpgrp(STDIN_FILENO, child_pid);
68 if (ret < 0) {
69 exit(1);
70 }
71
72 sleep(TIMEOUT_SECS);
73 exit(1);
74 }
75
76 T_DECL(pty_master_teardown,
77 "try removing a TTY master out from under a PTY slave holding a kevent",
78 T_META_ASROOT(true))
79 {
80 __block pid_t master_pid;
81 char buf[16] = "";
82 char *end;
83 ssize_t bytes_rd;
84 size_t buf_len = 0;
85 unsigned long slave_pid;
86 int master_fd;
87 char pty_filename[PATH_MAX];
88 int status;
89
90 T_SETUPBEGIN;
91 T_ASSERT_POSIX_SUCCESS(pipe(child_ready), NULL);
92
93 master_pid = forkpty(&master_fd, pty_filename, NULL, NULL);
94 if (master_pid == 0) {
95 pty_master();
96 __builtin_unreachable();
97 }
98 T_ASSERT_POSIX_SUCCESS(master_pid,
99 "forked child master PTY with pid %d, at pty %s", master_pid,
100 pty_filename);
101
102 close(child_ready[1]);
103
104 end = buf;
105 do {
106 bytes_rd = read(child_ready[0], end, sizeof(buf) - buf_len);
107 T_ASSERT_POSIX_SUCCESS(bytes_rd, "read on pipe between master and runner");
108 buf_len += (size_t)bytes_rd;
109 T_LOG("runner read %zd bytes", bytes_rd);
110 end += bytes_rd;
111 } while (bytes_rd != 0 && *(end - 1) != 's');
112
113 slave_pid = strtoul(buf, &end, 0);
114 if (buf == end) {
115 T_ASSERT_FAIL("could not parse child PID from master pipe");
116 }
117
118 T_LOG("got pid %lu for slave process from master", slave_pid);
119 T_SETUPEND;
120
121 T_LOG("sending fatal signal to master");
122 T_ASSERT_POSIX_SUCCESS(kill(master_pid, SIGKILL), NULL);
123
124 T_LOG("sending fatal signal to slave");
125 (void)kill((int)slave_pid, SIGKILL);
126
127 T_ASSERT_POSIX_SUCCESS(waitpid(master_pid, &status, 0), NULL);
128 T_ASSERT_TRUE(WIFSIGNALED(status), "master PID was signaled");
129 (void)waitpid((int)slave_pid, &status, 0);
130 }
131
132 volatile static bool writing = true;
133
134 static void *
135 reader_thread(void *arg)
136 {
137 int fd = (int)arg;
138 char c;
139
140 T_SETUPBEGIN;
141 T_QUIET;
142 T_ASSERT_GT(fd, 0, "reader thread received valid fd");
143 T_SETUPEND;
144
145 for (;;) {
146 ssize_t rdsize = read(fd, &c, sizeof(c));
147 if (rdsize == -1) {
148 if (errno == EINTR) {
149 continue;
150 } else if (errno == EBADF) {
151 T_LOG("reader got an error (%s), shutting down",
152 strerror(errno));
153 return NULL;
154 } else {
155 T_ASSERT_POSIX_SUCCESS(rdsize, "read on PTY");
156 }
157 } else if (rdsize == 0) {
158 return NULL;
159 }
160 }
161
162 return NULL;
163 }
164
165 static void *
166 writer_thread(void *arg)
167 {
168 int fd = (int)arg;
169 char c[4096];
170 memset(c, 'a', sizeof(c));
171
172 T_SETUPBEGIN;
173 T_QUIET;
174 T_ASSERT_GT(fd, 0, "writer thread received valid fd");
175 T_SETUPEND;
176
177 while (writing) {
178 ssize_t wrsize = write(fd, c, sizeof(c));
179 if (wrsize == -1) {
180 if (errno == EINTR) {
181 continue;
182 } else {
183 T_LOG("writer got an error (%s), shutting down",
184 strerror(errno));
185 return NULL;
186 }
187 }
188 }
189
190 return NULL;
191 }
192
193 #define ATTACH_ITERATIONS 10000
194
195 static int attach_master, attach_slave;
196 static pthread_t reader, writer;
197
198 static void
199 redispatch(dispatch_group_t grp, dispatch_source_type_t type, int fd)
200 {
201 __block int iters = 0;
202
203 __block void (^redispatch_blk)(void) = Block_copy(^{
204 if (iters++ > ATTACH_ITERATIONS) {
205 return;
206 } else if (iters == ATTACH_ITERATIONS) {
207 dispatch_group_leave(grp);
208 T_PASS("created %d %s sources on busy PTY", iters,
209 type == DISPATCH_SOURCE_TYPE_READ ? "read" : "write");
210 }
211
212 dispatch_source_t src = dispatch_source_create(
213 type, (uintptr_t)fd, 0,
214 dispatch_get_main_queue());
215
216 dispatch_source_set_event_handler(src, ^{
217 dispatch_cancel(src);
218 });
219
220 dispatch_source_set_cancel_handler(src, redispatch_blk);
221
222 dispatch_activate(src);
223 });
224
225 dispatch_group_enter(grp);
226 dispatch_async(dispatch_get_main_queue(), redispatch_blk);
227 }
228
229 T_DECL(attach_while_tty_wakeups,
230 "try to attach knotes while a TTY is getting wakeups")
231 {
232 dispatch_group_t grp = dispatch_group_create();
233
234 T_SETUPBEGIN;
235 T_ASSERT_POSIX_SUCCESS(openpty(&attach_master, &attach_slave, NULL, NULL,
236 NULL), NULL);
237
238 T_ASSERT_POSIX_ZERO(pthread_create(&reader, NULL, reader_thread,
239 (void *)(uintptr_t)attach_master), NULL);
240 T_ASSERT_POSIX_ZERO(pthread_create(&writer, NULL, writer_thread,
241 (void *)(uintptr_t)attach_slave), NULL);
242 T_SETUPEND;
243
244 redispatch(grp, DISPATCH_SOURCE_TYPE_READ, attach_master);
245 redispatch(grp, DISPATCH_SOURCE_TYPE_WRITE, attach_slave);
246
247 dispatch_group_notify(grp, dispatch_get_main_queue(), ^{
248 T_LOG("both reader and writer sources cleaned up");
249 T_END;
250 });
251
252 dispatch_main();
253 }
254
255 T_DECL(master_read_data_set,
256 "check that the data is set on read sources of master fds")
257 {
258 int master = -1, slave = -1;
259
260 T_SETUPBEGIN;
261 T_ASSERT_POSIX_SUCCESS(openpty(&master, &slave, NULL, NULL, NULL), NULL);
262 T_QUIET; T_ASSERT_GE(master, 0, "master fd is valid");
263 T_QUIET; T_ASSERT_GE(slave, 0, "slave fd is valid");
264
265 dispatch_source_t src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
266 (uintptr_t)master, 0, dispatch_get_main_queue());
267
268 dispatch_source_set_event_handler(src, ^{
269 unsigned long len = dispatch_source_get_data(src);
270 T_EXPECT_GT(len, (unsigned long)0,
271 "the amount of data to read was set for the master source");
272 dispatch_cancel(src);
273 });
274
275 dispatch_source_set_cancel_handler(src, ^{
276 dispatch_release(src);
277 T_END;
278 });
279
280 dispatch_activate(src);
281 T_SETUPEND;
282
283 // Let's not fill up the TTY's buffer, otherwise write(2) will block.
284 char buf[512] = "";
285
286 int ret = 0;
287 while ((ret = write(slave, buf, sizeof(buf)) == -1 && errno == EAGAIN)) {
288 ;
289 }
290 T_ASSERT_POSIX_SUCCESS(ret, "slave wrote data");
291
292 dispatch_main();
293 }