user1755094
user1755094

Reputation: 21

Need help handling "CTRL-C" signal in my simple shell program

I'm having some issues handling the "CTRL-C" signal, what I need is for handle_SIGINT to only output once, and then for it to return to the read in the setup function, but I don't know how to go about this. I was told to add a loop around the length=read call since read is a blocking call. I'm just confused on how to handle these signals. Anyways my current output is:

COMMAND->test
COMMAND->test2
COMMAND->test3
COMMAND->test4
COMMAND->^CFound your CTRL-C
Found your CTRL-C
COMMAND->Found your CTRL-C
COMMAND->Found your CTRL-C
COMMAND->error reading the command: Interrupted system call
^Z
Suspended


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */

void handle_SIGINT()
{
  printf("Found your CTRL-C\n");
}

void setup(char inputBuffer[], char *args[],int *background)
{
    int length, /* # of characters in the command line */
         i,      /* loop index for accessing inputBuffer array */
         start,  /* index where beginning of next command parameter is */
        ct;     /* index of where to place the next parameter into args[] */

     ct = 0;

     /* read what the user enters on the command line */
     length = read(STDIN_FILENO, inputBuffer, MAX_LINE);  

     start = -1;
     if (length == 0)
        exit(0);            /* ^d was entered, end of user command stream */
     if (length < 0){
        perror("error reading the command");
    exit(-1);           /* terminate with error code of -1 */
    }

     /* examine every character in the inputBuffer */
     for (i = 0; i < length; i++) { 
        switch (inputBuffer[i]){
        case ' ':
        case '\t' :               /* argument separators */
            if(start != -1){
                args[ct] = &inputBuffer[start];    /* set up pointer */
                ct++;
            }
            inputBuffer[i] = '\0'; /* add a null char; make a C string */
            start = -1;
            break;

        case '\n':                 /* should be the final char examined */
            if (start != -1){
                args[ct] = &inputBuffer[start];     
                ct++;
            }
            inputBuffer[i] = '\0';
            args[ct] = NULL; /* no more arguments to this command */
            break;

        case '&':
            *background = 1;
            inputBuffer[i] = '\0';
            break;

        default :             /* some other character */
            if (start == -1)
                start = i;
    } 
    }    
    args[ct] = NULL; /* just in case the input line was > 80 */
} 

int main(void)
{
    char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
    int background;             /* equals 1 if a command is followed by '&' */
    char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */

    struct sigaction handler;
    handler.sa_handler = handle_SIGINT;
    sigaction(SIGINT, &handler, NULL);

    while (1){            /* Program terminates normally inside setup */
        background = 0;
    printf("COMMAND->");
        fflush(0);
        setup(inputBuffer, args, &background);       /* get next command */

        int pid;

    pid = fork(); /* for a child process */

    if (pid < 0) { /* check if error occurred with child process */
      fprintf(stderr, "Fork Failed");
      return 1;
    } else if (pid == 0) { /* child process */
      execvp(args[0], args);
    } else {
      if (background == 0){ /* check if the parent should wait */
        //wait(args);
        waitpid(pid, NULL, 0);
      }
    }

    /* the steps are:
      (1) fork a child process using fork()
      (2) the child process will invoke execvp()
      (3) if background == 0, the parent will wait, 
        otherwise returns to the setup() function. */
    }
}

Upvotes: 1

Views: 6139

Answers (5)

Summer Lee
Summer Lee

Reputation: 281

As other guys said above, I just add a while-loop:

while((length = read(STDIN_FILENO, inputBuffer, MAX_LINE) == -1);  

Upvotes: 0

yeyo
yeyo

Reputation: 3009

Just as a recommendation, try making your programs as portable as possible, I mean, it seems to me that you can write a big deal of your program with ANSI C.

consider using fgets() instead of read() and don't never rely on behaviors, even if that behavior is well documented as bimda point it out.

void setup(char inputBuffer[], char *args[],int *background)
{
    char *ret; 
    int length; /* # of characters in the command line */
    int i;      /* loop index for accessing inputBuffer array */
    int start=-1;  /* index where beginning of next command parameter is */
    int ct=0;     /* index of where to place the next parameter into args[] */


     /* read what the user enters on the command line */
     ret = fgets(inputBuffer, MAX_LINE, stdin);  

     if(!ret)
        exit(0);            /* ^d was entered, end of user command stream */

    /* examine every character in the inputBuffer */
    for (i = 0; inputBuffer[i]; i++) {
            ...
    }
    ...
}

and the C-c part as patater said:

void handle_SIGINT()
{
    printf("Found your CTRL-C\n");
    signal(SIGINT, SIG_IGN);
}

~$ man signal

maybe it's not what you need, but that was just my humble opinion.

Upvotes: 0

askmish
askmish

Reputation: 6674

This is how you should've written using sigaction():

struct sigaction new_handler,old_handler;

new_handler.sa_handler=handle_SIGINT;
sigemptyset(&new_handler.sa_mask);
new_handler.sa_flags=0;

sigaction(SIGINT,NULL, &old_handler);
if(old_handler.sa_handler != SIG_IGN)
   sigaction(SIGINT, &new_handler,NULL);

Upvotes: 0

Patater
Patater

Reputation: 127

Tell your program to ignore the SIGINT signal in handle_SIGINT after printing your message.

void handle_SIGINT()
{
  printf("Found your CTRL-C\n");
  signal(SIGINT, SIG_IGN);
}

Upvotes: 1

dimba
dimba

Reputation: 27581

When read() return -1, check errno - If it is EINTR than return to perform read once more. EINTR indicates blocking system call (in our case read()) is interrupted by signal, so you can safely return to perform read one more

   EINTR  The call was interrupted by a signal before any data was read; see signal(7).

From other side, in many cases pressing CTRL+C indicated user wants to terminate program, so quitting the program is expected behavior.

Upvotes: 3

Related Questions