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