1

I am implementing my own fragment of code for a security control purpose. It normally runs in the background but on a timeout needs to take over the current terminal, display a message, collect user input, and react to the user input.

Waiting for the timeout is easy. Collecting user input while sleep is the active program is easy. Preventing the shell from stealing the user input I just tried to collect is not so easy.

I am reasonably convinced that "What if two programs did this?" doesn't apply. If another program triggered while we were waiting for input, it's not too bad. If the user wants to interfere with the security check, there are easier ways than this.

Joshua
  • 40,822
  • 8
  • 72
  • 132

1 Answers1

2

Need process group control. This is not so easy to find if you don't know that job control is implemented using process group control. The shell starts background processes in their own process groups and the bg and fg commands toggle which process group is allowed to read from the terminal. All other processes block reading from the terminal.

#include <unistd.h>

    sleep(600); /* triggering condition goes here */
    pid_t pgid = tcgetpgrp(0);
    pid_t pid;
    if ((pid = fork()) == 0) { /* Need to fork to safely create a new process group to bind to the terminal -- otherwise we might be in the same process group as we started in */
        pid_t npid = getpid();
        setpgid(npid, npid); /* create new process group and put us in it */
        pid_t pid2;
        if ((pid2 = fork() == 0) { /* what? another process */
            setpgid(getpid(), pgid);
            tcsetpgid(0, getpid()); /* set active process group */
            _exit(0);
        }
        if (pid2 > 0) {
            int junk;
            waitpid(pid2, &junk, 0);
        }
        struct termios savedattr;
        struct termios newattr;
        tcgetattr(0, &savedattr);
        newattr = savedattr;
        newattr.c_lflag |= ICANON | ECHO;
        tcsetattr(0, TCSANOW, &newattr); /* set sane terminal state */
        printf("\nHi there. I'm the background process and I want some input:");
        char buf[80];
        fgets(buf, 80, stdin);
        /* Do something with user input here */
        tcsetattr(0, TCSANOW, &savedattr); /* restore terminal state */
        tcsetpgrp(0, pgid); /* restore terminal owner -- only possible if the existing owner is a live process */
    } else {
        if (pid > 0) {
            int junk;
            waitpid(pid, &junk, 0);
        }
    }
Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 2
    In case its not clear from that block of code -- its the `tcsetpgrp` call that sets the controlling process group of the terminal. Only processes in the controlling process group can read from it; all others will get a SIGTTIN signal and stop. – Chris Dodd Aug 05 '18 at 20:40
  • "As a special case, if pid is 0, the process ID of the calling process shall be used. Also, if pgid is 0, the process ID of the indicated process shall be used." – Antti Haapala -- Слава Україні Mar 09 '19 at 21:28