/* * 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 #include #include #include #include #include #include #include #include #include #include #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); }