| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #if !defined _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <err.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/ioctl.h> |
| #include <sys/prctl.h> |
| #include <sys/wait.h> |
| #include <termios.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #define MAX_TEST_DURATION 60 |
| |
| time_t start_timer(void); |
| int timer_active(time_t timer_started); |
| |
| inline time_t start_timer() { |
| return time(NULL); |
| } |
| |
| inline int timer_active(time_t timer_started) { |
| return time(NULL) < (timer_started + MAX_TEST_DURATION); |
| } |
| |
| int main(void) { |
| sync(); /* we're probably gonna crash... */ |
| |
| // set test timer |
| const time_t timer_started = start_timer(); |
| |
| /* |
| * We may already be process group leader but want to be session leader; |
| * therefore, do everything in a child process. |
| */ |
| pid_t main_task = fork(); |
| if (main_task == -1) err(EXIT_FAILURE, "initial fork"); |
| if (main_task != 0) { |
| int status; |
| if (waitpid(main_task, &status, 0) != main_task) err(EXIT_FAILURE, "waitpid main_task"); |
| return WEXITSTATUS(status); |
| } |
| |
| printf("%d:test starts\n", getpid()); |
| |
| if (prctl(PR_SET_PDEATHSIG, SIGKILL)) err(EXIT_FAILURE, "PR_SET_PDEATHSIG"); |
| if (getppid() == 1) exit(EXIT_FAILURE); |
| |
| /* basic preparation */ |
| if (signal(SIGTTOU, SIG_IGN)) err(EXIT_FAILURE, "signal"); |
| if (setsid() == -1) err(EXIT_FAILURE, "start new session"); |
| |
| /* set up a new pty pair */ |
| int ptmx = open("/dev/ptmx", O_RDWR); |
| if (ptmx == -1) err(EXIT_FAILURE, "open ptmx"); |
| unlockpt(ptmx); |
| int tty = open(ptsname(ptmx), O_RDWR); |
| if (tty == -1) err(EXIT_FAILURE, "open tty"); |
| |
| /* |
| * Let a series of children change the ->pgrp pointer |
| * protected by the tty's ctrl_lock... |
| */ |
| pid_t child = fork(); |
| if (child == -1) { |
| err(EXIT_FAILURE, "fork"); |
| } |
| |
| // grandchildren creator process |
| if (child == 0) { |
| int ret = EXIT_SUCCESS; |
| if (prctl(PR_SET_PDEATHSIG, SIGKILL)) err(EXIT_FAILURE, "PR_SET_PDEATHSIG"); |
| if (getppid() == 1) exit(EXIT_FAILURE); |
| |
| while (timer_active(timer_started)) { |
| pid_t grandchild = fork(); |
| if (grandchild == -1) { |
| err(EXIT_FAILURE, "fork grandchild"); |
| } |
| if (grandchild == 0) { |
| if (setpgid(0, 0)) err(EXIT_FAILURE, "setpgid"); |
| int pgrp = getpid(); |
| if (ioctl(tty, TIOCSPGRP, &pgrp)) { |
| err(EXIT_FAILURE, "TIOCSPGRP (tty)"); |
| } |
| exit(EXIT_SUCCESS); |
| } |
| int status; |
| if (waitpid(grandchild, &status, 0) != grandchild) |
| err(EXIT_FAILURE, "waitpid for grandchild"); |
| if ((ret = WEXITSTATUS(status)) != EXIT_SUCCESS) { |
| break; |
| } |
| } // end while(time) |
| exit(ret); |
| } // end grandchildren creator process |
| |
| /* |
| * ... while the parent changes the same ->pgrp pointer under the |
| * ctrl_lock of the other side of the pty pair. |
| */ |
| int status; |
| const char* const TIOCSPGRP_ERROR = "TIOCSPGRP (ptmx)"; |
| const char* const WAITPID_ERROR = "waitpid for grandchildren creator"; |
| const char* message1 = NULL; |
| const char* message2 = NULL; |
| |
| while (timer_active(timer_started)) { |
| int pgrp = getpid(); |
| if (ioctl(ptmx, TIOCSPGRP, &pgrp)) { |
| message1 = TIOCSPGRP_ERROR; |
| break; |
| } |
| } |
| |
| // wait for grandchildren creator to complete |
| if (waitpid(child, &status, 0) != child) { |
| message2 = WAITPID_ERROR; |
| } |
| |
| // return exit status |
| if (message1 != NULL || message2 != NULL) { |
| err(EXIT_FAILURE, "%s %s", message1 != NULL ? message1 : "", |
| message2 != NULL ? message2 : ""); |
| } |
| printf("%d:test completed\n", getpid()); |
| return WEXITSTATUS(status); |
| } |