]>
Commit | Line | Data |
---|---|---|
d9a64523 A |
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 *********************************/ | |
287 |