Search code examples
clinuxttyjob-control

How do I make a background process block for input a la the shell's `bg` command?


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.


Solution

  • 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);
            }
        }