CS-PROJECTS / job_control / src / cush.c
cush.c
Raw
/*
 * cush - the customizable shell.
 *
 * Developed by Godmar Back for CS 3214 Summer 2020
 * Virginia Tech.  Augmented to use posix_spawn in Fall 2021.
 */
#define _GNU_SOURCE 1
#define PROMPT_LENGTH 100
#define W 1
#define R 0

#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdbool.h>
#include <fcntl.h>

/* Since the handed out code contains a number of unused functions. */
#pragma GCC diagnostic ignored "-Wunused-function"

#include "termstate_management.h"
#include "signal_support.h"
#include "shell-ast.h"
#include "utils.h"

enum job_status
{
    FOREGROUND,    /* job is running in foreground.  Only one job can be
                      in the foreground state. */
    BACKGROUND,    /* job is running in background */
    STOPPED,       /* job is stopped via SIGSTOP */
    NEEDSTERMINAL, /* job is stopped because it was a background job
                      and requires exclusive terminal access */
    DONE           /* jobs is completed and ready to be removed from the job_list*/
};

enum valid_commands
{
    JOBS,        /* displays a list of current jobs */
    FG,          /* brings job to the foreground */
    BG,          /* throws job to the background */
    KILL,        /* sends KILL signal to a specified job */
    STOP,        /* stops the specified job */
    EXIT,        /* exits the cush shell */
    CD,          /* switches to specified directory */
    HISTORY,     /* displays the command history list */
    EXCLAMATION, /* helps run commands from command history */
    INVALID
};

struct job
{
    struct list_elem elem;           /* Link element for jobs list. */
    struct ast_pipeline *pipe;       /* The pipeline of commands this job represents */
    bool complete;                   /* Denotes whether all commands within the pipeline are
                                         comepleted. */
    int jid;                         /* Job id. */
    enum job_status status;          /* Job status. */
    int num_processes_alive;         /* The number of processes that we know to be alive */
    struct termios *saved_tty_state; /* The state of the terminal when this job was
                                       stopped after having been in foreground */
    pid_t group;                     /* Group id */
    pid_t *pid;                      /* holds all PID's for processes in a given job */
};

int pipe2(int pipefd[2], int flags);
static void handle_child_status(pid_t pid, int status);
enum valid_commands is_valid_command(struct list_elem *pipe);
static bool throw_to_execute(struct ast_command_line *cline, struct termios *shell_state);
static void execute_job(struct ast_pipeline *curr, struct ast_command *cmd, struct termios *shell_state);
static void execute_job_piped(struct ast_pipeline *curr, struct termios *tty_state);
static void sigstp_handler(int sig_num);

/* Utility functions for job list management.
 * We use 2 data structures:
 * (a) an array jid2job to quickly find a job based on its id
 * (b) a linked list to support iteration
 */
#define MAXJOBS (1 << 16)
static struct list job_list;
static struct job *jid2job[MAXJOBS];
static int num_of_jobs;      /* denotes the number of jobs currently in the job_list */
static struct job *most_rec; /* pointer to the most recent jobs if jid is not passed with certain commands */
static int comm_number = 0;  /* tracks the command number of the current session */
static int fg_job_jid;       /* the jid of the most recent fg job */

static void
usage(char *progname)
{
    printf("Usage: %s -h\n"
           " -h            print this help\n",
           progname);

    exit(EXIT_SUCCESS);
}

/* Build a prompt */
static char *
build_prompt(void)
{
    comm_number++;
    char host[PROMPT_LENGTH / 2];

    if (gethostname(host, PROMPT_LENGTH / 2) == -1)
        perror("gethostname"), exit(EXIT_FAILURE);

    char dir[PROMPT_LENGTH / 2], *dir_pt = dir;
    char *cwd = get_current_dir_name();
    dir_pt = basename(cwd);
    char user[PROMPT_LENGTH / 2], *user_pt = user;
    user_pt = getlogin();
    char prompt[PROMPT_LENGTH * 2];

    snprintf(prompt, PROMPT_LENGTH, "[%d] %s@%s in %s > ", comm_number, user_pt, host, dir_pt);
    free(cwd);

    return strdup(prompt);
}

/* Return job corresponding to jid */
static struct job *
get_job_from_jid(int jid)
{
    if (jid > 0 && jid < MAXJOBS && jid2job[jid] != NULL)
        return jid2job[jid];

    return NULL;
}

/* Add a new job to the job list */
static struct job *
add_job(struct ast_pipeline *pipe)
{
    struct job *job = malloc(sizeof(*job));
    job->pipe = pipe;
    job->complete = false;
    job->pid = calloc(list_size(&pipe->commands), sizeof(pid_t));
    job->status = (job->pipe->bg_job) ? BACKGROUND : FOREGROUND;

    list_push_back(&job_list, &job->elem);

    for (int i = 1; i < MAXJOBS; i++)
    {
        if (jid2job[i] == NULL)
        {
            jid2job[i] = job;
            job->jid = i;
            most_rec = job;
            num_of_jobs++;
            return job;
        }
    }

    fprintf(stderr, "Maximum number of jobs exceeded\n");
    abort();
    return NULL;
}

/* Delete a job.
 * This should be called only when all processes that were
 * forked for this job are known to have terminated.
 */
static void
delete_job(struct job *job)
{
    int jid = job->jid;
    assert(jid != -1);
    jid2job[jid]->jid = -1;
    jid2job[jid] = NULL;
    ast_pipeline_free(job->pipe);
    free(job);
    num_of_jobs--;
}

/* Returns a string representing the status of a job */
static const char *
get_status(enum job_status status)
{
    switch (status)
    {
    case FOREGROUND:
        return "Foreground";
    case BACKGROUND:
        return "Running";
    case STOPPED:
        return "Stopped";
    case NEEDSTERMINAL:
        return "Stopped (tty)";
    case DONE:
        return "DONE";
    default:
        return "Unknown";
    }
}

/* Print the command line that belongs to one job. */
static void
print_cmdline(struct ast_pipeline *pipeline)
{
    struct list_elem *e = list_begin(&pipeline->commands);
    for (; e != list_end(&pipeline->commands); e = list_next(e))
    {
        struct ast_command *cmd = list_entry(e, struct ast_command, elem);
        if (e != list_begin(&pipeline->commands))
            printf("| ");
        char **p = cmd->argv;
        printf("%s", *p++);
        while (*p)
            printf(" %s", *p++);
    }
}

/* Print a job */
static void
print_job(struct job *job)
{
    printf("[%d]\t%s\t\t(", job->jid, get_status(job->status));

    for (struct list_elem *cmdLink = list_begin(&job->pipe->commands);
         cmdLink != list_end(&job->pipe->commands); cmdLink = list_next(cmdLink))
    {
        struct ast_command *cmd = list_entry(cmdLink, struct ast_command, elem);

        int i = 0;
        while (cmd->argv[i])
        {
            printf("%s", cmd->argv[i]);
            if ((cmd->argv[++i]))
                printf(" ");
        }

        if (list_size(&job->pipe->commands) > 1 && cmdLink->next != list_end(&job->pipe->commands))
            printf(" | ");
    }

    printf(")\n");
}

/* checks if all commands within the job pipeline are complete*/
static bool
is_job_complete(struct job *job)
{
    return (list_empty(&job->pipe->commands));
}

/* Removes jobs from the job_list and prints completion status if job was ran in background*/
static void
remove_completed()
{
    if (list_empty(&job_list))
        return;

    struct list_elem *job_node = list_begin(&job_list);
    while (!list_empty(&job_list) && list_end(&job_list) != job_node)
    {
        struct job *curr_job = list_entry(job_node, struct job, elem);

        if (is_job_complete(curr_job))
        {
            curr_job->status = DONE;

            if (curr_job->pipe->bg_job)
                printf("[%d]\t%s\n", curr_job->jid, get_status(curr_job->status));

            job_node = job_node->prev;
            list_remove(job_node->next);
            delete_job(curr_job);
        }
        job_node = list_next(job_node);
    }
}

/*
 * Wait for all processes in this job to complete, or for
 * the job no longer to be in the foreground.
 * You should call this function from a) where you wait for
 * jobs started without the &; and b) where you implement the
 * 'fg' command.
 *
 * Implement handle_child_status such that it records the
 * information obtained from waitpid() for pid 'child.'
 *
 * If a process exited, it must find the job to which it
 * belongs and decrement num_processes_alive.
 *
 * However, note that it is not safe to call delete_job
 * in handle_child_status because wait_for_job assumes that
 * even jobs with no more num_processes_alive haven't been
 * deallocated.  You should postpone deleting completed
 * jobs from the job list until when your code will no
 * longer touch them.
 *
 * The code below relies on `job->status` having been set to FOREGROUND
 * and `job->num_processes_alive` having been set to the number of
 * processes successfully forked for this job.
 */
static void
wait_for_job(struct job *job)
{
    assert(signal_is_blocked(SIGCHLD));

    int status;
    pid_t child = waitpid(-1, &status, WUNTRACED);

    // When called here, any error returned by waitpid indicates a logic
    // bug in the shell.
    // In particular, ECHILD "No child process" means that there has
    // already been a successful waitpid() call that reaped the child, so
    // there's likely a bug in handle_child_status where it failed to update
    // the "job" status and/or num_processes_alive fields in the required
    // fashion.
    // Since SIGCHLD is blocked, there cannot be races where a child's exit
    // was handled via the SIGCHLD signal handler.

    if (child != -1)
        handle_child_status(child, status);
    else
        utils_fatal_error("waitpid failed, see code for explanation");
}

/*
 * Suggested SIGCHLD handler.
 *
 * Call waitpid() to learn about any child processes that
 * have exited or changed status (been stopped, needed the
 * terminal, etc.)
 * Just record the information by updating the job list
 * data structures.  Since the call may be spurious (e.g.
 * an already pending SIGCHLD is delivered even though
 * a foreground process was already reaped), ignore when
 * waitpid returns -1.
 * Use a loop with WNOHANG since only a single SIGCHLD
 * signal may be delivered for multiple children that have
 * exited. All of them need to be reaped.
 */
static void
sigchld_handler(int sig, siginfo_t *info, void *_ctxt)
{
    pid_t child;
    int status;

    assert(sig == SIGCHLD);

    while ((child = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0)
        handle_child_status(child, status);
}

/* Signal handler for all child processes */
static void
handle_child_status(pid_t pid, int status)
{
    assert(signal_is_blocked(SIGCHLD));

    for (struct list_elem *job_node = list_begin(&job_list);
         job_node != list_end(&job_list); job_node = list_next(job_node))
    {
        struct job *job = list_entry(job_node, struct job, elem);

        for (struct list_elem *command = list_begin(&job->pipe->commands);
             command != list_end(&job->pipe->commands); command = list_next(command))
        {
            struct ast_command *cmd = list_entry(command, struct ast_command, elem);

            if (pid == cmd->pid)
            {
                if (WIFEXITED(status))
                    list_remove(command);
                else if (WSTOPSIG(status) == SIGTSTP) // Ctrl Z
                {
                    cmd->stopped = true;
                    job->status = STOPPED;
                    job->pipe->bg_job = true;
                    print_job(job);
                    if (killpg(job->group, SIGTSTP) != 0)
                        perror("killpg");
                    fg_job_jid = -1;
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGINT) // Ctrl C
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGINT) != 0)
                        perror("killpg");
                    printf("%d\n", WIFSIGNALED(status));
                    fg_job_jid = -1;
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGKILL) // kill()
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGKILL) != 0)
                        perror("killpg");
                    printf("killed\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGABRT) // abort()
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGABRT) != 0)
                        perror("killpg");
                    printf("aborted\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGSEGV) // seg fault
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGSEGV) != 0)
                        perror("killpg");
                    printf("segmentation fault\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGFPE) // FPE
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGFPE) != 0)
                        perror("killpg");
                    printf("floating point exception\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGSYS) // killed
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGSYS) != 0)
                        perror("killpg");
                    printf("killed\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WTERMSIG(status) == SIGTERM) // terminated
                {
                    job->complete = true;
                    job->status = DONE;
                    if (killpg(job->group, SIGTERM) != 0)
                        perror("killpg");
                    printf("terminated\n");
                    termstate_give_terminal_back_to_shell();
                }
                else if (WSTOPSIG(status) == SIGTTOU || WSTOPSIG(status) == SIGTTIN) // needs terminal
                {
                    job->complete = false;
                    job->status = NEEDSTERMINAL;
                    job->pipe->bg_job = true;
                    if (killpg(job->group, SIGTTOU) != 0)
                        perror("killpg");
                    termstate_give_terminal_back_to_shell();
                }
            }
        }
    }
}

/* Helper to convert list_elem within a pipe ot an ast_command */
static struct ast_command *
convert_to_ast_command(struct list_elem *pipe)
{
    struct ast_pipeline *pipe_list = list_entry(pipe, struct ast_pipeline, elem);
    struct list_elem *cmd = list_begin(&pipe_list->commands);
    struct ast_command *command = list_entry(cmd, struct ast_command, elem);

    return command;
}

/* Starts a specified job in the background */
static void
push_to_bg(struct job *curr_job)
{
    curr_job->status = BACKGROUND;
    curr_job->pipe->bg_job = true;
    fg_job_jid = -1;
    printf("[%d]  %d\n", curr_job->jid, curr_job->group);
    if (killpg(curr_job->group, SIGCONT) != 0)
        perror("killpg"), exit(EXIT_FAILURE);
}

/* Starts a specified job in the forground */
static void
push_to_fg(struct job *curr_job, struct termios *tty_state)
{
    print_cmdline(curr_job->pipe);
    printf("\n");

    fg_job_jid = curr_job->jid;
    if (killpg(curr_job->group, SIGCONT) != 0)
        perror("killpg"), exit(EXIT_FAILURE);
    curr_job->status = FOREGROUND;

    if (curr_job->pipe->bg_job)
    {
        termstate_give_terminal_to(NULL, curr_job->group);
        curr_job->saved_tty_state = get_save();
        curr_job->pipe->bg_job = false;
    }
    else
        termstate_restore(curr_job->saved_tty_state);

    signal_block(SIGCHLD);
    wait_for_job(curr_job);
    signal_unblock(SIGCHLD);
    termstate_give_terminal_back_to_shell();
}

// Signal Handler for SIGTSTP (Ctr Z)
static void
sigstp_handler(int sig_num)
{
    if (fg_job_jid == -1)
        exit(EXIT_SUCCESS);
}

// Signal Handler for SIGINT (Ctr C)
static void
sigint_handler(int sig_num)
{
    if (fg_job_jid == -1)
        return;
}

/* checks if argument is a valid, built-in command */
enum valid_commands
is_valid_command(struct list_elem *pipe)
{
    struct ast_command *entry = convert_to_ast_command(pipe);
    char *command = entry->argv[0];

    if (!strcmp(command, "jobs")) /* only needs command */
        return JOBS;
    if (!strcmp(command, "fg")) /* fg JobID or fg JobID, ... */
        return FG;
    if (!strcmp(command, "bg")) /* bg JobID or bg JobID, ... */
        return BG;
    if (!strcmp(command, "exit")) /* exit [N] if just exit use last command executed status. */
        return EXIT;
    if (!strcmp(command, "kill")) /* kill JobID */
        return KILL;
    if (!strcmp(command, "stop"))
        return STOP;
    if (!strcmp(command, "cd"))
        return CD;
    if (!strcmp(command, "history"))
        return HISTORY;
    if (strchr(command, '!'))
        return EXCLAMATION;

    return INVALID;
}

/* Executes all builtin commands */
static bool
execute_command(enum valid_commands command,
                char **command_args,
                struct list_elem *pipe,
                struct termios *tty_state,
                int jid)
{
    struct job *job = get_job_from_jid(jid);

    switch (command)
    {
    case EXIT:
        return true;

    case JOBS:
        for (struct list_elem *job_node = list_begin(&job_list);
             job_node != list_end(&job_list); job_node = list_next(job_node))
        {
            struct job *curr_job = list_entry(job_node, struct job, elem);

            if (!is_job_complete(curr_job))
                print_job(curr_job);
        }
        break;

    case FG:
        if (job->status == FOREGROUND)
            printf("%d already in forground.\n", jid);
        else
            push_to_fg(job, tty_state);
        break;

    case BG:
        switch (job->status)
        {
        case FOREGROUND:
            push_to_bg(job);
            break;
        case BACKGROUND:
            printf("%d already in background.\n", jid);
            break;
        case STOPPED:
            push_to_bg(job);
            break;
        case NEEDSTERMINAL:
            printf("%d must be in foreground.\n", jid);
            break;
        case DONE:
            break;
        }
        break;

    case STOP:
        if (killpg(job->group, SIGTSTP) != 0)
            perror("killpg"), exit(EXIT_FAILURE);
        job->status = STOPPED;
        signal_block(SIGCHLD);
        wait_for_job(job);
        signal_unblock(SIGCHLD);
        break;

    case KILL:
        if (!is_job_complete(job))
        {
            if (killpg(job->group, SIGKILL) != 0)
                perror("killpg"), exit(EXIT_FAILURE);
            list_remove(&job->elem);
        }
        break;

    case CD:
        (command_args[1] != NULL) ? chdir(command_args[1]) : chdir(getenv("HOME"));
        break;

    case HISTORY:;
        register HIST_ENTRY **history;
        if ((history = history_list()))
        {
            int range = (command_args[1] != NULL) ? atoi(command_args[1]) : history_length;
            range = (!range || range > history_length) ? history_length : range;
            int start = (history_length - range);
            if (start >= 0 && command_args[1] != NULL)
                for (register int i = start; i < history_length; i++)
                    printf("%d: %s\n", i + 1, history[i]->line);
            else
                for (register int i = 0; history[i]; i++)
                    printf("%d: %s\n", i + history_base, history[i]->line);
        }
        break;

    case EXCLAMATION:;
        char *arg = command_args[0] + 1;
        register HIST_ENTRY *cmd;
        int n, start_offset = where_history();

        if (!strcmp(arg, "!"))
        {
            cmd = previous_history();
            history_set_pos(start_offset);
        }
        else if ((n = atoi(arg)) != 0)
            cmd = history_get(n);
        else
        {
            while ((cmd = previous_history()))
                if ((strstr(cmd->line, arg) != NULL))
                    break;
            history_set_pos(start_offset);
        }

        if (cmd)
        {
            struct ast_command_line *line = ast_parse_command_line(cmd->line);
            throw_to_execute(line, tty_state);
        }
        break;

    case INVALID:
        break;

    default:
        return false;
    }
    return false;
}

/*
 * Forks a new process and calls launch command to start a job.
 * Sets the process group id and pid for the child process in our jobs list
 */
static void execute_job(struct ast_pipeline *curr, struct ast_command *cmd, struct termios *shell_state)
{
    struct job *new_job = add_job(curr);
    new_job->pid = calloc(1, sizeof(pid_t));
    fg_job_jid = (new_job->pipe->bg_job) ? -1 : new_job->jid;

    cmd->pid = fork();

    // in child
    if (cmd->pid == 0)
    {
        cmd->pid = getpid();
        new_job->pid[0] = cmd->pid;
        new_job->group = (new_job->group == 0) ? getpid() : new_job->group;

        if (curr->iored_input != NULL)
        {
            FILE *input = fopen(curr->iored_input, "r");
            if (dup2(fileno(input), STDIN_FILENO) < 0)
                perror("dup2"), exit(EXIT_FAILURE);
            curr->iored_input = NULL;
            close(fileno(input));
        }
        if (curr->iored_output)
        {
            char *mode = (curr->append_to_output || cmd->dup_stderr_to_stdout) ? "a" : "w";
            FILE *output = fopen(curr->iored_output, mode);
            dup2(fileno(output), STDOUT_FILENO);
            if (cmd->dup_stderr_to_stdout)
                if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
                    perror("dup2"), exit(EXIT_FAILURE);
            close(fileno(output));
        }

        if (execvp(cmd->argv[0], cmd->argv) < 0)
        {
            delete_job(new_job);
            perror("execvp");
            exit(EXIT_SUCCESS);
        }
    }
    // in parent
    else
    {
        new_job->group = cmd->pid;
        if (setpgid(cmd->pid, new_job->group) < 0)
            perror("setpgid"), exit(EXIT_FAILURE);

        new_job->status = (new_job->pipe->bg_job) ? BACKGROUND : FOREGROUND;

        // foreground child
        if (!new_job->pipe->bg_job)
        {
            termstate_give_terminal_to(NULL, cmd->pid);
            new_job->saved_tty_state = get_save();
            signal_block(SIGCHLD);
            wait_for_job(new_job);
            signal_unblock(SIGCHLD);
        }
        // background child
        else
            printf("[%d]  %d\n", new_job->jid, new_job->group);

        termstate_give_terminal_back_to_shell();
    }
}

/*
 * Forks a new process and calls launch command to start a job.
 * Sets the process group id and pids for the child processes in our jobs list
 * This accounts for piped commands and redirected io
 */
static void
execute_job_piped(struct ast_pipeline *curr, struct termios *tty_state)
{
    struct job *new_job = add_job(curr);
    new_job->pid = calloc(list_size(&curr->commands), sizeof(pid_t));

    bool append_output = false;

    int num_pipes = list_size(&curr->commands) - 1;
    int pipe_arr[num_pipes][2];

    for (int i = 0; i < num_pipes; i++)
    {
        if (pipe2(pipe_arr[i], O_CLOEXEC) == -1)
            perror("pipe2"), exit(EXIT_FAILURE);
    }

    int job_cmd_num = 0;
    struct list_elem *command = list_begin(&curr->commands);

    while (command != list_end(&curr->commands))
    {
        struct ast_command *cmd = list_entry(command, struct ast_command, elem);

        // in child
        cmd->pid = fork();
        if (cmd->pid == 0)
        {
            if (job_cmd_num != 0) // not first
            {
                if (dup2(pipe_arr[job_cmd_num - 1][R], R) < 0)
                    perror("dup2"), exit(EXIT_FAILURE);
            }

            if (job_cmd_num != num_pipes) // not last
            {
                if (dup2(pipe_arr[job_cmd_num][W], W) < 0)
                    perror("dup2"), exit(EXIT_FAILURE);
            }

            // iored input only on first command
            if (curr->iored_input && job_cmd_num == 0)
            {
                FILE *input = fopen(curr->iored_input, "r");
                if (dup2(fileno(input), STDIN_FILENO) < 0)
                    perror("dup2"), exit(EXIT_FAILURE);
            }

            // iored
            if (curr->iored_output && job_cmd_num == num_pipes)
            {
                char *mode = (curr->append_to_output || cmd->dup_stderr_to_stdout) ? "a" : "w";
                append_output = cmd->dup_stderr_to_stdout ? true : append_output;
                FILE *output = fopen(curr->iored_output, mode);
                if (dup2(fileno(output), STDOUT_FILENO) < 0)
                    perror("dup2"), exit(EXIT_FAILURE);

                if (cmd->dup_stderr_to_stdout)
                {
                    if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
                        perror("dup2"), exit(EXIT_FAILURE);
                }
            }

            new_job->pid[job_cmd_num] = getpid();
            if (job_cmd_num != 0)
            {
                if (setpgid(new_job->pid[job_cmd_num], new_job->group) < 0)
                    perror("setpgid"), exit(EXIT_FAILURE);
            }
            if (job_cmd_num == 0)
            {
                if (setpgid(cmd->pid, 0) < 0)
                    perror("setpgid"), exit(EXIT_FAILURE);
                new_job->group = cmd->pid;
            }
            if (execvp(cmd->argv[0], cmd->argv) < 0)
                perror("execvp"), exit(EXIT_SUCCESS);
        }
        if (job_cmd_num == 0)
        {
            if (setpgid(cmd->pid, 0) < 0)
                perror("setpgid"), exit(EXIT_FAILURE);
            new_job->group = cmd->pid;
        }

        job_cmd_num++;
        command = list_next(command);
    }

    // in parent
    for (int i = 0; i < num_pipes; i++)
    {
        close(pipe_arr[i][R]);
        close(pipe_arr[i][W]);
    }

    // foreground child
    if (!new_job->pipe->bg_job)
    {
        fg_job_jid = new_job->jid;
        termstate_give_terminal_to(NULL, new_job->group);
        new_job->saved_tty_state = get_save();
        signal_block(SIGCHLD);
        wait_for_job(new_job);
        signal_unblock(SIGCHLD);
    }
    // background child
    else
        printf("[%d]  %d\n", new_job->jid, new_job->group);

    termstate_give_terminal_back_to_shell();
}

/* Decides whether user input is a job or builtin command and sends said input to its correct handler */
static bool
throw_to_execute(struct ast_command_line *cline, struct termios *shell_state)
{
    struct list_elem *pipe_elem = list_begin(&cline->pipes);
    int command;

    if ((command = is_valid_command(pipe_elem)) != INVALID)
    {
        struct ast_command *cmd = convert_to_ast_command(pipe_elem);
        int most_recent_jid = (most_rec == NULL) ? 0 : most_rec->jid;
        int jid = (cmd->argv[1] != NULL) ? atoi(cmd->argv[1]) : most_recent_jid;
        return execute_command(command, cmd->argv, pipe_elem, shell_state, jid);
    }
    else
    {
        struct ast_pipeline *job_pipe = list_entry(pipe_elem, struct ast_pipeline, elem);
        struct ast_command *cmd = convert_to_ast_command(pipe_elem);
        list_remove(pipe_elem);

        bool piped = list_size(&job_pipe->commands) > 1;

        if (piped)
            execute_job_piped(job_pipe, shell_state);
        else
            execute_job(job_pipe, cmd, shell_state);
    }
    return false;
}

/*main method*/
int main(int ac, char *av[])
{
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTSTP, sigstp_handler);
    signal(SIGINT, sigint_handler);
    signal_set_handler(SIGCHLD, sigchld_handler);

    termstate_init();
    struct termios *shell_state = get_save();

    /* Process command-line arguments. See getopt(3) */
    int opt;
    while ((opt = getopt(ac, av, "h")) > 0)
    {
        switch (opt)
        {
        case 'h':
            usage(av[0]);
            break;
        }
    }

    list_init(&job_list);
    num_of_jobs = 0;

    using_history();

    /* Read/eval loop. */
    for (;;)
    {
        remove_completed();
        fg_job_jid = -1;

        /* Do not output a prompt unless shell's stdin is a terminal */
        char *prompt = isatty(0) ? build_prompt() : NULL;
        char *cmdline = readline(prompt);
        free(prompt);

        if (cmdline == NULL) /* User typed EOF */
            break;

        struct ast_command_line *cline = ast_parse_command_line(cmdline);

        if (cline == NULL) /* Error in command line */
        {
            free(cmdline);
            continue;
        }

        if (list_empty(&cline->pipes)) /* User hit enter */
        {
            ast_command_line_free(cline);
            free(cmdline);
            continue;
        }

        /* Adds to command history */
        char *expansion;
        int result = history_expand(cmdline, &expansion);

        if (result)
            fprintf(stderr, "%s\n", expansion);
        if (result < 0 || result == 2)
            free(expansion);
        else
            add_history(expansion);

        /* Executes the job or command */
        if (throw_to_execute(cline, shell_state))
        {
            ast_command_line_free(cline);
            free(cmdline);
            rl_clear_history();
            clear_history();
            exit(EXIT_SUCCESS);
        }

        if (!expansion)
            free(expansion);

        ast_command_line_free(cline);
        free(cmdline);
    }
    rl_clear_history();
    return 0;
}