customBashShell / 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. */
static 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);
    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);
}

pid_t
termstate_get_current_terminal_owner(void)
{
    pid_t rc = tcgetpgrp(termstate_get_tty_fd());
    if (rc == -1)
        utils_fatal_error("tcgetpgrp: ");

    return rc;
}