123
123

Reputation: 8951

Can successfully trap CTRL-Z, but not SIGTSTP

I've written a shell in C that does has some basic functionality. I've implemented a 'foreground-only mode' in my program which is toggled with SIGTSTP or CTRL-Z. However, I am able to trap the CTRL-Z signal, but not the SIGTSTP signal. How is this possible? Isn't CTRL-Z the same thing as SIGTSTP? Shouldn't trapping one trap the other?

I trap the signal (I realize signal() is deprecated, but I have tried sigaction() as well, and the problem persists.):

signal(SIGTSTP, trapTstp);

And handle it with this function:

void trapTstp() {
    if(foregroundMode == 0) {
        write(1, "Entering foreground-only mode (& is now ignored)\n", 49);
        write(1, ": ", 2);
        fflush(stdout);
        foregroundMode = 1;
    } else {
        write(1, "Exiting foreground-only mode\n", 29);
        write(1, ": ", 2);
        fflush(stdout);
        foregroundMode = 0;
    }
}

If I run my program and hit CTRL-Z, I can successfully toggle in and out of foreground-only mode. However, I cannot trap SIGTSTP. For example, if I run sleep 100, get the PID for it, and run kill -SIGTSTP sleep_pid, the sleep 100 process gets killed early with the output Terminated: 15 indicating it was in fact killed, and thus the SIGTSTP signal was not trapped.

Here is my full shell program:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>

int statusCode;
int foregroundMode = 0;
int bg = 0;
int bgPsArray[20];
int bgPsCount = 0;
int i;
char line[256];

pid_t popBgProcess() {
    int size = sizeof(bgPsArray)/sizeof(bgPsArray[0]);
    if (size > 0) {
      return bgPsArray[size+1];
    } else {
        return 0;
    }
}

void trapInterrupt(int _) {
    int childStatus;
    pid_t child;
    while ((child = popBgProcess())) {
        if(child != getpid()) {
            kill(child, SIGKILL);
            waitpid(child, &childStatus, 0);
        }
    }
}

void trapTstp() {
    if(foregroundMode == 0) {
        write(1, "Entering foreground-only mode (& is now ignored)\n", 49);
        write(1, ": ", 2);
        fflush(stdout);
        foregroundMode = 1;
    } else {
        write(1, "Exiting foreground-only mode\n", 29);
        write(1, ": ", 2);
        fflush(stdout);
        foregroundMode = 0;
    }
}

int getCommand() {
    printf(": ");
    fflush(stdout);
    if(fgets(line, sizeof(line), stdin) != NULL) {
        char *position = strchr(line, '\n');
    *position = '\0'; // Replace '\n' with '\0'
        if(foregroundMode == 1) { // Foreground mode on
            if((position = strchr(line, '&')) != NULL) {
                *position = '\0'; // Replace '&' with '\0'
            }
            bg = 0; // Ignore '&' so do not create background process
        } else { // Foreground mode off
            if((position = strchr(line, '&')) != NULL) {
                *position = '\0'; // Replace '&' with '\0'
                bg = 1; // Is a background process
            } else {
                bg = 0;
            }
        }
  } else { // If input is null
        return 0;
  }
    return 1;
}

void checkProcessCompletion() {
    int status;
    for(i=0; i<bgPsCount; i++) {
        if(waitpid(bgPsArray[i], &status, WNOHANG) > 0) {
            if(WIFEXITED(status)) { // If exit
                printf("Background PID %d is done: exit value %d\n", bgPsArray[i], WEXITSTATUS(status));
                fflush(stdout);
            } else if(WIFSIGNALED(status)) { // If signal
                printf("Background PID %d is done: terminated by signal %d\n", bgPsArray[i], WTERMSIG(status));
                fflush(stdout);
            }
        }
    }
}

int runCommand(int cmd) {
    if(cmd == 0) { // Return if there was no command
        return 0;
    } else if(strcmp(line, "exit") == 0) {
        exit(0);
    } else if(strstr(line, "#")) { // Comment input (do nothing)
    } else if(strcmp(line, "status") == 0) {
        printf("exit value %d\n", statusCode);
        fflush(stdout);
    }
    else if(strncmp("cd", line, strlen("cd")) == 0) {
        if(line[2] == ' ') { // If space after 'cd' expect directory
            char cwd[1024];
            getcwd(cwd, sizeof(cwd));
            char *path = strstr(line, " ");
            if(path) {
                path += 1;
                char *value;
                value = malloc(strlen(path));
                memcpy(value, path, strlen(path));
                *(value + strlen(path)) = 0;
                sprintf(cwd, "%s/%s", cwd, value); // Directory to change to
                free(value);
            }
            chdir(cwd); // cd to new directory
        } else { // cd with no argument
            char *home = getenv("HOME");
            chdir(home); // cd to HOME directory
        }
    }
    else { // System commands
        pid_t pid, ppid;
        int status;
        char *command;
        char *args[256];
        int argCount;
        command = strtok(line, " ");

        // Create args array for execvp
        args[0] = command;
        argCount = 1;
        args[argCount] = strtok(NULL, " ");
        while(args[argCount] != NULL) { // Add arguments to array
            argCount++;
            args[argCount] = strtok(NULL, " ");
        }
        if((pid = fork()) < 0) { // Fork fails
            perror("fork");
            fflush(stdout);
            exit(1);
        }
        if(pid == 0) { // Child process
            for(i=0; i<argCount; i++) {
                if(strcmp(args[i], "<") == 0) { // Redirecting input
                    if(access(args[i+1], R_OK) == -1) { // File is unreadable
                        perror("access");
                        fflush(stdout);
                    } else { // File is readable
                        int file = open(args[i+1], O_RDONLY, 0);
                        dup2(file, STDIN_FILENO);
                        close(file);
                        execvp(command, &command);
                    }
                }
                else if(strcmp(args[i], ">") == 0) { // Redirecting output
                    int file = creat(args[i+1], 7777);
                    dup2(file, STDOUT_FILENO);
                    close(file);
                    execvp(command, args);
                } else { // No redirection
                    execvp(command, args);
                }
            }
            perror("execvp"); // Error for execvp
            exit(1);
        } else { // Parent process
            if (bg == 1) { // Background process
                int status;
                int process;
                printf("Background PID: %d\n", pid);
                fflush(stdout);
                bgPsArray[bgPsCount] = pid; // Add process to background process array
                bgPsCount++;
                process = waitpid(pid, &status, WNOHANG);
            } else { // Foreground process
                int status;
                waitpid(pid, &status, 0); // Wait on the process
                if(WIFEXITED(status)) {
                    statusCode = WEXITSTATUS(status);
                }
            }
        }
    }
    return 1;
}

int main(int argc, char *argv[], char *envp[]) {
    // Creating 'junk' manually is necessary because output redirection is broken,
    // and a large portion of the grading script is depedent upon it's existence.
    FILE *fp = fopen("junk", "ab+");
    const char *text;
    fprintf(fp, "Junk in junkfile\n");
    fclose(fp);
    signal(SIGINT, trapInterrupt);
    signal(SIGTSTP, trapTstp);
    while(1) {
        checkProcessCompletion(); //Check the processes
        int cmd = getCommand(); // Get command from user
        int result = runCommand(cmd);
        if (result == 0) {
            break;
        }
    }
    return 0;
}

Upvotes: 1

Views: 2384

Answers (1)

Jean-Baptiste Yun&#232;s
Jean-Baptiste Yun&#232;s

Reputation: 36401

First you must not trap SIGTSTP for the shell itself (it should ignore it), only for its child. Second if you really want to write a job controller shell, you need to manage children with the help of process group and correctly set the foreground group. Writing a shell that behave correctly with job control is a heavy task. Read POSIX standard about shells, groups, sessions, terminal control.

About your current problem. If your sub process does an exec then each handled signal is reset to its default behavior. This is because exec recovers the old code with the new one, so the previously set handler is no more available. Now you must let the child behave normally against TSTP and just let the parent track its status either with a synchronous call to wait/waitpid or with the asynchronous help of SIGCHLD. When the child stops or terminates, the parent is able to see it in the returned status (WIFEXITED, WIFSIGNALED, WIFSTOPPED).

Upvotes: 1

Related Questions