0

I'm trying to handle multiple signals with one signal handler, the expected result is for ctrlc to exit the child process and also exit the parent process while ctrlz prints a random number everytime ctrlz is pressed but it doesn't seem to work after the first signal is handled.The other part of the code is a child process that loops until ctrl-c is called. This is the code.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#include <string.h>

int sig_ctrlc = 0;
int sig_ctrlz = 0;

//main signal handler to handle all signals
void SIGhandler(int sig) {
  switch (sig) {
    case SIGINT:
      sig_ctrlc = SIGINT;
      break;

    case SIGTSTP:
      sig_ctrlz = SIGTSTP;
      break;

    case SIGCHLD:
    default: break;
  }
}


int main() {
  int fd[2]; //to store the two ends of the pipe
  char get_inode[] = "ls -il STATUS.TXT";
  FILE *fp;
  FILE *log;
  FILE *command;
  time_t t;
  time(&t);
  int rv = 0;
  log = fopen("file_log.txt", "w");
  struct sigaction act;
  memset (&act, 0, sizeof(struct sigaction));
  act.sa_handler = SIGhandler;

  //if pipe can't be created
  if (pipe(fd) < 0) {
    fprintf(log, "Pipe error");
  }
    int pid = fork();
  switch(pid) {

      case -1:
          fprintf(stderr, "fork failed\n");
          exit(1);
      case 0:
        /*child process */

//          maps STDOUT to the writing end of the pipe
//           if  (dup2(fd[1], STDOUT_FILENO) == -1) {
//             fprintf(log, "error in mapping stdout to the writing pipe\n");
//           }
          act.sa_flags = SA_RESTART;
          sigemptyset(&act.sa_mask);
          sigaction(SIGINT, &act, NULL);
          sigaction(SIGTSTP, &act, NULL);

          for (size_t i = 1;; i++) {
              /* code */
              printf(" running in child\n");
              sleep(1);
              if (sig_ctrlc != 0) {
                printf("ctrlc handled\n");
                printf("exiting...\n");
                sig_ctrlc = 0;
                break;
              }

              if (sig_ctrlz != 0) {
                printf("ctlrz handled.\n");
                /* random generator, the problem with this is it runs with time if ctrlz
                    is handled within a second it returns the same number
                */
                srand(time(0));
                int rand_num;
                rand_num = rand() % (50 - 10 + 1) + 10;
                printf("random number: %d\n", rand_num);
                sig_ctrlz = 0;
                sigaction(SIGINT, &act, NULL);
                sigaction(SIGTSTP, &act, NULL);
              }
          }

      default:
          /* parent process */
          close(fd[1]);
          //maps STDIN to the reading end of the pipe
          // if (dup2(fd[0], STDIN_FILENO) < 0) {
          //   fprintf(log, "can't redirect");
          //   exit(1);
          // }

//          //checks for fopen not working and writes to STATUS.TXT with a redirect
//          if ((fp = freopen("STATUS.TXT", "w", stdout)) != NULL) {
//            printf("start time of program: %s\n",  ctime(&t));
//            printf("Parent process ID: %d\n", getppid());
//            printf("Child process ID: %d\n", getpid());
//
//            //gets inode information sends the command to and receives the info from the terminal
//            command = popen(get_inode, "w");
//            fprintf(command, "STATUS.TXT");
//            fclose(command);
//
////            map STDOUT to the status file
//            if(freopen("STATUS.TXT", "a+ ", stdout) == NULL) {
//                fp = fopen("file_log.txt","w");
//                fprintf(log, "can't map STATUS.TXT to stdout\n");
//                exit(1);
//            }
//
          printf("parent has started\n");

          wait(NULL);
          time(&t);
          printf("PARENT: My child's termination status is: %d at: %s\n", WEXITSTATUS(rv), ctime(&t));
//            fprintf(fp, "PARENT: My child's termination status is: %d at: %s\n", WEXITSTATUS(rv), ctime(&t));
//          fclose(fp);
//          fclose(log);

          sigaction(SIGINT, &act, NULL);

          for (size_t i = 1;; i++) {
              /* code */
              printf("PARENT: in parent function\n");
              sleep(1);
              if (sig_ctrlc != 0)
                  exit(0);
          }

  }
    return 0;
}
Victor
  • 9
  • 2
  • 1
    I think you're missing a `break;` or `exit()` at the end of your `case 0:`. As it stands, after handling the signal and breaking out of the loop, the child falls through into the `default:` case and runs the code intended for the parent. – Nate Eldredge Mar 24 '21 at 02:10
  • If that's not the problem, then please explain your issue more clearly. What did you type after running the program, what output did you see, and what output were you expecting? – Nate Eldredge Mar 24 '21 at 02:11
  • You should check the return value from `wait()` to ensure you got the details of the right corpse. It looks as though you should be calling `int corpse = wait(&rv);` so that you get the exit status of the child rather than unconditionally seeing `0`. – Jonathan Leffler Mar 24 '21 at 02:35
  • What do you mean by "_does not work_"? Does it work for the first `Ctrl-Z`, but not for following ones? -- You might like to read more about signals. Especially that when a handler is triggered, it is [implementation-defined](https://en.cppreference.com/w/c/program/signal) whether the default is restored. – the busybee Mar 24 '21 at 08:44
  • @thebusybee after any signal is handled, all signals including the signal handled can't be handled again (if ctrlc or ctrlz is pressed it doesn't do anything) – Victor Mar 24 '21 at 15:26
  • @NateEldredge the child process is meant to continue running until ctrlc is pressed, but if ctrlz is pressed when in the child process, it's meant to print out a random number. my output prints a random number only once and doesn't handle both ctrlc and ctrlz after the first ctrlz – Victor Mar 24 '21 at 15:31
  • Well, as I said, you need to do some more research to know how to solve this correctly. It seems that you need to set the signal handler again in the signal handler. ;-) – the busybee Mar 24 '21 at 20:18

1 Answers1

0

There are some good comments on the original post that help make minor fixes. I think there is also an issue of the static variables sig_ctrlc and sig_ctrlz maybe not being async-signal safe. Other than that though, I think your signal handling setup should work in a case where you repeatedly send SIGTSTP and then SIGINT after. I think how you are going about testing your program may be the issue.

Based on some clues you've given:

  • "ctrlz is pressed but it doesn't seem to work after the first signal is handled"
  • "doesn't handle both ctrlc and ctrlz after the first ctrlz"

It leads me to believe that what you are experiencing is actually the terminal's job control getting in your way. This sequence of events may explain it:

  • parent (process A) is started in terminal foreground in group %1
  • child (process B) is forked and also in terminal foreground in group %1
  • signal handlers are set up within child
  • in an attempt to signal the child, press ctrl-z to send SIGTSTP
  • owning terminal (grandparent of child (process B) in this case) receives the request
  • owning terminal broadcasts the signal to all processes in the foreground group
  • owning terminal removes group %1 from foreground
  • parent (process A) receives SIGTSTP and is suspended (default action)
  • child (process B) receives SIGTSTP and the signal handler is invoked
  • the random number is generated and printed on next iteration of child loop
  • subsequent attempts to signal the child via ctrl-z or ctrl-c are not forwarded to the child (or parent) by the terminal because nothing is in the terminal foreground

If that was indeed the case, at that point, you should be able to bring the processes back to the foreground by manually typing in fg and hitting enter. You could then try and signal again. However, a better way to test a program like this would be to run it in one terminal, then send the signals via kill(...) using their pid's from another terminal.

One extra note: unlike signal(...), sigaction(...) does not require "re-installation" after each disposition. A good explanation by Jonathan here https://stackoverflow.com/a/232711/7148416

diametralpitch
  • 675
  • 3
  • 5
  • Thanks, I see what you mean by typing fg and hitting enter, could you explain in detail what you mean with the sending of signals via kill – Victor Mar 25 '21 at 04:50
  • Yes, once you identify the process ID of the running child (via ps, pgrep, etc) you can from another terminal (assuming bash) run `kill -TSTP 12345` where "12345" is replaced with the actual process ID. – diametralpitch Mar 25 '21 at 05:39