28.6.4 Foreground and Background

Now let’s consider what actions must be taken by the shell when it
launches a job into the foreground, and how this differs from what
must be done when a background job is launched.

When a foreground job is launched, the shell must first give it access
to the controlling terminal by calling tcsetpgrp. Then, the
shell should wait for processes in that process group to terminate or
stop. This is discussed in more detail in Stopped and Terminated Jobs.

When all of the processes in the group have either completed or stopped,
the shell should regain control of the terminal for its own process
group by calling tcsetpgrp again. Since stop signals caused by
I/O from a background process or a SUSP character typed by the user
are sent to the process group, normally all the processes in the job
stop together.

The foreground job may have left the terminal in a strange state, so the
shell should restore its own saved terminal modes before continuing. In
case the job is merely stopped, the shell should first save the current
terminal modes so that it can restore them later if the job is
continued. The functions for dealing with terminal modes are
tcgetattr and tcsetattr; these are described in
Terminal Modes.

Here is the sample shell’s function for doing all of this.

/* Put job j in the foreground. If cont is nonzero,restore the saved terminal modes and send the process group aSIGCONT signal to wake it up before we block. */
void
put_job_in_foreground (job *j, int cont)
{
/* Put the job into the foreground. */
tcsetpgrp (shell_terminal, j->pgid);