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