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