]> git.saurik.com Git - apple/xnu.git/blob - tests/tty_hang.c
xnu-4903.270.47.tar.gz
[apple/xnu.git] / tests / tty_hang.c
1 /*
2 * Copyright (c) 2015-2018 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #include <unistd.h>
26 #include <sys/ioctl.h>
27 #include <sys/types.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <util.h>
32 #include <syslog.h>
33 #include <termios.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <sys/socket.h>
37 #include <sys/stat.h>
38
39 #include <darwintest.h>
40 #include <darwintest_utils.h>
41 #include <darwintest_multiprocess.h>
42
43 #define TEST_TIMEOUT 10
44
45 /*
46 * Receiving SIGTTIN (from the blocked read) is the passing condition, we just
47 * catch it so that we don't get terminated when we receive this.
48 */
49 void
50 handle_sigttin(int signal)
51 {
52 return;
53 }
54
55 /*
56 * Because of the way dt_fork_helpers work, we have to ensure any children
57 * created by this function calls exit instead of getting the fork handlers exit
58 * handling
59 */
60 int
61 get_new_session_and_terminal_and_fork_child_to_read(char *pty_name)
62 {
63 int sock_fd[2];
64 int pty_fd;
65 pid_t pid;
66 char buf[10];
67
68 /*
69 * We use this to handshake certain actions between this process and its
70 * child.
71 */
72 T_ASSERT_POSIX_SUCCESS(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd),
73 NULL);
74
75 /*
76 * New session, lose any existing controlling terminal and become
77 * session leader.
78 */
79 T_ASSERT_POSIX_SUCCESS(setsid(), NULL);
80
81 /* now open pty, become controlling terminal of new session */
82 T_ASSERT_POSIX_SUCCESS(pty_fd = open(pty_name, O_RDWR), NULL);
83
84 T_ASSERT_POSIX_SUCCESS(pid = fork(), NULL);
85
86 if (pid == 0) { /* child */
87 int pty_fd_child;
88 char buf[10];
89
90 T_ASSERT_POSIX_SUCCESS(close(sock_fd[0]), NULL);
91 T_ASSERT_POSIX_SUCCESS(close(pty_fd), NULL);
92
93 /* Make a new process group for ourselves */
94 T_ASSERT_POSIX_SUCCESS(setpgid(0, 0), NULL);
95
96 T_ASSERT_POSIX_SUCCESS(pty_fd_child = open(pty_name, O_RDWR),
97 NULL);
98
99 /* now let parent know we've done open and setpgid */
100 write(sock_fd[1], "done", sizeof("done"));
101
102 /* wait for parent to set us to the foreground process group */
103 read(sock_fd[1], buf, sizeof(buf));
104
105 /*
106 * We are the foreground process group now so we can read
107 * without getting a SIGTTIN.
108 *
109 * Once we are blocked though (we have a crude 1 second sleep on
110 * the parent to "detect" this), our parent is going to change
111 * us to be in the background.
112 *
113 * We'll be blocked until we get a signal and if that is signal
114 * is SIGTTIN, then the test has passed otherwise the test has
115 * failed.
116 */
117 signal(SIGTTIN, handle_sigttin);
118 (void)read(pty_fd_child, buf, sizeof(buf));
119 /*
120 * If we get here, we passed, if we get any other signal than
121 * SIGTTIN, we will not reach here.
122 */
123 exit(0);
124 }
125
126 T_ASSERT_POSIX_SUCCESS(close(sock_fd[1]), NULL);
127
128 /* wait for child to open slave side and set its pgid to its pid */
129 T_ASSERT_POSIX_SUCCESS(read(sock_fd[0], buf, sizeof(buf)), NULL);
130
131 /*
132 * We need this to happen and in the order shown
133 *
134 * parent (pgid = pid) child (child_pgid = child_pid)
135 *
136 * 1 - tcsetpgrp(child_pgid)
137 * 2 - block in read()
138 * 3 - tcsetpgrp(pgid)
139 *
140 * making sure 2 happens after 1 is easy, we use a sleep(1) in the
141 * parent to try and ensure 3 happens after 2.
142 */
143
144 T_ASSERT_POSIX_SUCCESS(tcsetpgrp(pty_fd, pid), NULL);
145
146 /* let child know you have set it to be the foreground process group */
147 T_ASSERT_POSIX_SUCCESS(write(sock_fd[0], "done", sizeof("done")), NULL);
148
149 /*
150 * give it a second to do the read of the terminal in response.
151 *
152 * XXX : Find a way to detect that the child is blocked in read(2).
153 */
154 sleep(1);
155
156 /*
157 * now change the foreground process group to ourselves -
158 * Note we are now in the background process group and we need to ignore
159 * SIGTTOU for this call to succeed.
160 *
161 * Hopefully the child has gotten to run and blocked for read on the
162 * terminal in the 1 second we slept.
163 */
164 signal(SIGTTOU, SIG_IGN);
165 T_ASSERT_POSIX_SUCCESS(tcsetpgrp(pty_fd, getpid()), NULL);
166
167 return 0;
168 }
169
170 /*
171 * We're running in a "fork helper", we can't do a waitpid on the child because
172 * the fork helper unhelpfully hides the pid of the child and in it kills itself.
173 * We will instead fork first and wait on the child. If it is
174 * able to emerge from the read of the terminal, the test passes and if it
175 * doesn't, the test fails.
176 * Since the test is testing for a deadlock in proc_exit of the child (caused
177 * by a background read in the "grandchild".
178 */
179 void
180 run_test(int do_revoke)
181 {
182 int master_fd;
183 char *slave_pty;
184 pid_t pid;
185
186 T_WITH_ERRNO;
187 T_QUIET;
188
189 T_SETUPBEGIN;
190
191 slave_pty = NULL;
192 T_ASSERT_POSIX_SUCCESS(master_fd = posix_openpt(O_RDWR | O_NOCTTY),
193 NULL);
194 (void)fcntl(master_fd, F_SETFL, O_NONBLOCK);
195 T_ASSERT_POSIX_SUCCESS(grantpt(master_fd), NULL);
196 T_ASSERT_POSIX_SUCCESS(unlockpt(master_fd), NULL);
197 slave_pty = ptsname(master_fd);
198 T_ASSERT_NOTNULL(slave_pty, NULL);
199 T_LOG("slave pty is %s\n", slave_pty);
200
201 T_SETUPEND;
202
203 /*
204 * We get the stdin and stdout redirection but we don't have visibility
205 * into the child (nor can we wait for it). To get around that, we fork
206 * and only let the parent to the caller and the child exits before
207 * returning to the caller.
208 */
209 T_ASSERT_POSIX_SUCCESS(pid = fork(), NULL);
210
211 if (pid == 0) { /* child */
212 T_ASSERT_POSIX_SUCCESS(close(master_fd), NULL);
213 get_new_session_and_terminal_and_fork_child_to_read(slave_pty);
214
215 /*
216 * These tests are for testing revoke and read hangs. This
217 * revoke can be explicit by a revoke(2) system call (test 2)
218 * or as part of exit(2) of the session leader (test 1).
219 * The exit hang is the common hang and can be fixed
220 * independently but fixing the revoke(2) hang requires us make
221 * changes in the tcsetpgrp path ( which also fixes the exit
222 * hang). In essence, we have 2 fixes. One which only addresses
223 * the exit hang and one which fixes both.
224 */
225 if (do_revoke) {
226 /* This should not hang for the test to pass .. */
227 T_ASSERT_POSIX_SUCCESS(revoke(slave_pty), NULL);
228 }
229 /*
230 * This child has the same dt_helper variables as its parent
231 * The way dt_fork_helpers work if we don't exit() from here,
232 * we will be killing the parent. So we have to exit() and not
233 * let the dt_fork_helpers continue.
234 * If we didn't do the revoke(2), This test passes if this exit
235 * doesn't hang waiting for its child to finish reading.
236 */
237 exit(0);
238 }
239
240 int status;
241 int sig;
242
243 dt_waitpid(pid, &status, &sig, 0);
244 if (sig) {
245 T_FAIL("Test failed because child received signal %s\n",
246 strsignal(sig));
247 } else if (status) {
248 T_FAIL("Test failed because child exited with status %d\n",
249 status);
250 } else {
251 T_PASS("test_passed\n");
252 }
253 /*
254 * we can let this process proceed with the regular darwintest process
255 * termination and cleanup.
256 */
257 }
258
259
260 /*************************** TEST 1 ********************************/
261 T_HELPER_DECL(create_new_session_and_exit, "create_new_session_and_exit") {
262 run_test(0);
263 }
264
265 T_DECL(tty_exit_bgread_hang_test, "test for background read hang on ttys with proc exit")
266 {
267 dt_helper_t helpers[1];
268
269 helpers[0] = dt_fork_helper("create_new_session_and_exit");
270 dt_run_helpers(helpers, 1, TEST_TIMEOUT);
271 }
272 /*********************** END TEST 1 ********************************/
273
274 /************************** TEST 2 ***********************************/
275 T_HELPER_DECL(create_new_session_and_revoke_terminal, "create_new_session_and_revoke_terminal") {
276 run_test(1);
277 }
278
279 T_DECL(tty_revoke_bgread_hang_test, "test for background read hang on ttys with revoke")
280 {
281 dt_helper_t helpers[1];
282
283 helpers[0] = dt_fork_helper("create_new_session_and_revoke_terminal");
284 dt_run_helpers(helpers, 1, TEST_TIMEOUT);
285 }
286 /*********************** END TEST 2 *********************************/