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