KenMil426
KenMil426

Reputation: 13

Having problems working with SIGINT signal in C / UNIX

I have this C program that runs a basic shell for UNIX and I am looking to do something specific. Essentially whenever I press CTRL+C I am looking to kill all the child processes, but keep the parent process alive in order to loop back for the next line of input. The code below seems to be working as intended for basic commands like date,whoami,etc. However when I issue the following commands to test, the shell does not loop back to get more input and any text entered afterwards does nothing.

$sleep 100 
$CTRL+C

In order to stop the shell I have to use CTRL+Z to force the program to stop. I need the SIGINT signal to interrupt the child's sleep command and force the parent to return to prompting the user after killing the child processes, but obviously i have done something wrong. Here is the output to explain what I mean.

$ ./a3shell2
--------myShell > date
 Input command is: date
Sat Nov  9 15:38:08 CST 2019
--------myShell > whoami
 Input command is: whoami
klm46
--------myShell > ^C
Caught SIGINT!
killing children
date
 Input command is: date
Sat Nov  9 15:38:20 CST 2019
--------myShell > sleep 100
 Input command is: sleep 100
^C
Caught SIGINT!
killing children
date
date
whoami
^Z
[2]+  Stopped                 ./a3shell2

The Shell Program:

/* ----------------------------------------------------------------- */
/* PROGRAM  simple shell.c progam                                    */
/* ----------------------------------------------------------------- */
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>

pid_t pid = 0;

void sig_int(int signo) 
{
    printf("\nCaught SIGINT!\n");
  printf("killing children\n"); 
  if (pid != 0) 
  {
    kill(pid, SIGKILL); 
    pid = 0; 
  }
}

char *getinput(char *buffer, size_t buflen) 
{
    printf("--------myShell > ");
    return fgets(buffer, buflen, stdin);
}

/* ----------------------------------------------------------------- */
/* FUNCTION  parse:                                                  */
/* ----------------------------------------------------------------- */

void  parse(char *line, char **argv)
{
     while (*line != '\0') {       /* if not the end of line ....... */ 
          while (*line == ' ' || *line == '\t' || *line == '\n')
               *line++ = '\0';     /* replace white spaces with 0    */
          *argv++ = line;          /* save the argument position     */
          while (*line != '\0' && *line != ' ' && 
                 *line != '\t' && *line != '\n') 
               line++;             /* skip the argument until ...    */
     }
     *argv = '\0';                 /* mark the end of argument list  */
}

/* ----------------------------------------------------------------- */
/* FUNCTION execute:                                                 */
/* ----------------------------------------------------------------- */

void execute(char **argv)
{
     //pid_t  pid;
     int    status;

     if ((pid = fork()) < 0) {     /* fork a child process           */
          printf("*** ERROR: forking child process failed\n");
          exit(1);
     }
     else if (pid == 0) {          /* for the child process:         */
          if (execvp(*argv, argv) < 0) {     /* execute the command  */
               printf("*** ERROR: exec failed\n");
               exit(1);
          }
     }
     else {                                  /* for the parent:      */
          while (wait(&status) != pid)       /* wait for completion  */
               ;
     }
}

/* ----------------------------------------------------------------- */
/*                  The main program starts here                     */
/* ----------------------------------------------------------------- */

void  main(void)
{

     char  line[1024];             /* the input line                 */
     char  *argv[64];              /* the command line argument      */
     size_t linelen;

if (signal(SIGINT, sig_int) == SIG_ERR) 
  {
        fprintf(stderr, "signal error: %s\n", strerror(errno));
        exit(1);
    }

     while (1) 
     {                   /* repeat until done ....         */
          getinput(line, sizeof(line));
          line[strlen(line) - 1] = '\0';
          printf(" Input command is: %s \n", line);

          parse(line, argv);       /*   parse the line               */

          if (strcmp(argv[0], "exit") == 0)  /* is it an "exit"?     */
               exit(0);            /*   exit if it is                */

          execute(argv);           /* otherwise, execute the command */
     }
}

Upvotes: 0

Views: 744

Answers (1)

pilcrow
pilcrow

Reputation: 58701

Your shell is stuck in an endless wait(&status) != pid loop when you CTRL+C.

The SIGINT interrupts the wait(), and your signal handler sets the global pid variable to zero. When control returns to the loop, it will never end: pid is zero, and wait() can only return PIDs and -1, never zero.

(You don't see this behavior with date and whoami because those child processes have already terminated by the time you issue CTRL+C. You are probably interrupting fgets() in this case, not wait(), and sending a SIGKILL to an already reaped PID. That's not good, either, as there is the possibility that PID will be re-used when you send the SIGKILL.)

As an aside, it's not clear what actually happens when the SIGINT interrupts your wait() call, as signal() has historically varied behavior on different platforms. The call might be interrupted, returning -1 (EINTR), or it might resume the wait(), returning the PID of the kill()ed child. It's moot in this case — neither of those match the zero you're looking for at that point — but is a good reason to use sigaction() and not signal().

Upvotes: 1

Related Questions