lfalkau
lfalkau

Reputation: 916

Handle signals and process writing my own shell

I'm trying to make a shell that prompt a command and wait for user input. I want my shell to print another prompt when the user press ctrl-c and exit when he presses ctrl-d.

Here's the main loop:

int my_shell(char **env)
{
    char    *cmd_line;

    while (true)
    {
            print_prompt();
            cmd_line = get_cmd();
            process(cmd_line);
    }
    return (0);
}

I'm able to catch ctrl-c and ctrl-d signals but I don't know how to structure the main loop to exit at the good places. I tried using several fork(), wait(), getpid() but I'm doing it wrong.

Here's one of my try:


int extern_pid;
int intern_pid;

int my_shell(char **env)
{
    char    *cmd_line;

    extern_pid = getpid();
    while (true)
    {
        if (fork() == 0)
        {
            intern_pid = getpid();
            print_prompt();
            cmd_line = get_cmd();
            process(cmd_line);
            exit(0);
        }
        else
        {
            wait(0);
        }
    }
    return (0);
}

And with those signals handlers:

void ctrlc_handler(int signal)
{
    if (getpid() == intern_pid)
        exit(0);
}

void ctrld_handler(int signal)
{
    if (getpid() == extern_pid)
        exit(0);
}

note: the ctrl-d signal is handled in the get_cmd() function.

Upvotes: 0

Views: 1179

Answers (1)

Stephan Schlecht
Stephan Schlecht

Reputation: 27106

One doesn't have to create a child process with fork to process the Ctrl-C signal in a custom shell. One possibility would be to use a signal handler together with sigsetjmp/siglongjmp.

The procedure would be as follows:

  • the signal handler is installed
  • before the main loop is started, the call environment is stored in env using sigsetjmp
  • in the signal handler, the call siglongjmp() restores the environment saved by sigsetjmp() and performs a non-local jump to the location where sigsetjmp was called

Since signal handlers can always be called, possibly even before calling sigsetjmp(), it must be ensured that siglongjmp() can be already called. This is done by setting a volatile variable sig_atomic_t called jmp_set.

The function process knows only one internal command called exit. As already noted in the comments to the question, if the user enters Ctrl-D as the first character at the beginning of a line, this automatically results in an EOF in the getchar call. The function get_cmd then returns the command exit here. Alternatively, the user can enter the command exit to end the program.

In the function process the position is marked with a comment where you probably want to call external programs with fork/exec. It returns a bool whether the program should be quit or not. Possibly one could evaluate here also a status code from the calls of the external programs accordingly.

Here is a small, self-contained example program with your ctrlc_handler, get_cmd and process function layout, extended with sigsetjmp()/siglongjmp(), not complete of course, but maybe a starting point:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>

#define MAX_CMD_LENGTH 1024

static const char *const EXIT_CMD = "exit";
static sigjmp_buf env;
static volatile sig_atomic_t jmp_set;

static void ctrlc_handler(int signal) {
    if (jmp_set == 0)
        return;
    if (signal == SIGINT) {
        siglongjmp(env, 1);
    }
}

static char *get_cmd(void) {
    char *cmd_buf = calloc(MAX_CMD_LENGTH, sizeof(char));
    if (!cmd_buf) {
        perror("malloc failed\n");
        exit(1);
    }

    char *ptr = cmd_buf;
    int ch = getchar();
    while (ch != EOF && ch != '\n' && ptr < cmd_buf + MAX_CMD_LENGTH - 1) {
        *ptr++ = (char) ch;
        ch = getchar();
    }
    if (ch == EOF) {
        strcpy(cmd_buf, EXIT_CMD);
    }
    return cmd_buf;

}

static bool process(char *cmd) {
    if (strcmp(cmd, EXIT_CMD) == 0) {
        return true;
    }
    // a call to fork together with a function from the
    // exec family could be used to call external programs
    printf("process '%s'\n", cmd);
    return false;
}

static int cnt = 0;

int main(void) {
    if (signal(SIGINT, ctrlc_handler) == SIG_ERR) {
        perror("signal error");
        exit(1);
    }
    if (sigsetjmp(env, 1)) {
        printf("\n");
        cnt++;
    }
    jmp_set = 1;
    bool exit;
    do {
        printf("%d> ", cnt);
        char *cmd = get_cmd();
        exit = process(cmd);
        free(cmd);
    } while (!exit);
    return 0;
}

Upvotes: 2

Related Questions