blob: b8cc096719f884cf79204b0d277429a610a09ddd [file] [log] [blame]
/*
* 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);
}