CS-PROJECTS / job_control / src / termstate_management.c
termstate_management.c
Raw
/*
 * Utility functions to support managing the terminal
 * state for a job control shell.
 *
 * Refactored for CS 3214 Summer 2020 Virginia Tech.
 */

#include <termios.h>
#include <errno.h>
#include <stddef.h>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#include "termstate_management.h"
#include "utils.h"
#include "signal_support.h"

static int terminal_fd = -1;           /* The controlling terminal */
static struct termios saved_tty_state; /* The state of the terminal when shell
                                           was started. */
static int shell_pgrp;                 /* The pgrp of the shell when it started */

/* Initialize tty support. */
void termstate_init(void)
{
    char *tty;
    assert(terminal_fd == -1 || !!!"termstate_init already called");

    terminal_fd = open(tty = ctermid(NULL), O_RDWR);
    if (terminal_fd == -1)
        utils_fatal_error("opening controlling terminal %s failed: ", tty);

    if (utils_set_cloexec(terminal_fd))
        utils_fatal_error("cannot mark terminal fd FD_CLOEXEC");

    shell_pgrp = getpgrp();
    termstate_sample();
}

/* Save current terminal settings.
 * This function is used when a job is suspended.*/
void termstate_save(struct termios *saved_tty_state)
{
    int rc = tcgetattr(terminal_fd, saved_tty_state);
    if (rc == -1)
        utils_fatal_error("tcgetattr failed: ");
}

/* Restore terminal to saved settings.
 * This function is used when resuming a suspended job. */
void termstate_restore(struct termios *saved_tty_state)
{
    int rc;

retry:
    rc = tcsetattr(terminal_fd, TCSADRAIN, saved_tty_state);
    if (rc == -1)
    {
        /* tcsetattr, apparently, does not restart even with SA_RESTART,
         * so repeat call on EINTR. */
        if (errno == EINTR)
            goto retry;

        utils_fatal_error("could not restore tty attributes tcsetattr: ");
    }
}

/* Get a file descriptor that refers to controlling terminal */
int termstate_get_tty_fd(void)
{
    assert(terminal_fd != -1 || !!!"termstate_init() must be called");
    return terminal_fd;
}

/**
 * Assign ownership of the terminal to process group
 * pgrp, restoring its terminal state if provided.
 *
 * Before printing a new prompt, the shell should
 * invoke this function with its own process group
 * id (obtained on startup via getpgrp()) and a
 * sane terminal state (obtained on startup via
 * termstate_init()).
 */
void termstate_give_terminal_to(struct termios *pg_tty_state, pid_t pgrp)
{
    signal_block(SIGTTOU);
    //When fd refers to the controlling terminal of the calling
    //process, the function tcgetpgrp() will return the foreground
    //process group ID of that terminal if there is one, and some value
    //larger than 1 that is not presently a process group ID otherwise.
    //When fd does not refer to the controlling terminal of the calling
    //process, -1 is returned, and errno is set to indicate the error.
    int rc = tcsetpgrp(termstate_get_tty_fd(), pgrp);
    if (rc == -1)
        utils_fatal_error("tcsetpgrp: ");

    if (pg_tty_state)
        termstate_restore(pg_tty_state);
    signal_unblock(SIGTTOU);
}

void termstate_give_terminal_back_to_shell(void)
{
    assert(shell_pgrp > 0 || !!!"termstate_init was not called");
    termstate_give_terminal_to(&saved_tty_state, shell_pgrp);
}

void termstate_sample(void)
{
    termstate_save(&saved_tty_state);
}

struct termios *
get_save(void)
{
    return &saved_tty_state;
}