2

I am trying to implement some of the features present in the shell including quitting only when a user enters quit and not on Ctrl+C. Below is a simplified version of the code that I am tried.

Code 1: without calling loop() in a signal handler.

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

The output of Code 1:

enter image description here

As can be seen on a third input, I go to a new line and continued right from where I left the loop. I want to instead start the loop again. So, I did the following modification by calling loop in signal handler itself.

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
    loop();
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

Output for code 2:

enter image description here

As can be seen that when I clicked first-time Ctrl+C (on input line 3), it works properly and I can continue. But when I click Ctrl+C second time I don't go to a new line and I have to press enter for a program to execute.

I went through this question but it doesn't seem to apply for my case. This is my first time using signals and system calls so my question may come as silly. But it will be a great help if someone can help me to get to a proper way of implementing signals. Thank you.

Mandar Sadye
  • 689
  • 2
  • 9
  • 30
  • you are changing the disposition of SIGINT inside the main **and** inside your sigintHandler function – J.Panek Aug 13 '19 at 15:33
  • @J.Panek Sorry but can you elaborate, please. I am sorry if I am not picking on an obvious mistake. I didn't get what you are trying to say. In both programs statement calling signalHandler seem to be identical. – Mandar Sadye Aug 13 '19 at 15:39
  • You call signal in your main and change the behavior of SIGINT, then inside the handler you call signal again and change SIGINT again. Only call it from inside the main in your case. Leave only the `printf("\n");` inside `sigintHandle` – J.Panek Aug 13 '19 at 15:46
  • 2
    Don't use `signal()` — use `sigaction()` (see [What is the difference between `sigaction()` and `signal()`?](https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal)). Don't use `printf()` in a signal handler; it can lead to undefined behaviour (see [How to avoid using `printf()` in a signal handler](https://stackoverflow.com/questions/16891019/)). Don't use `loop()` in the signal handler — it is completely unsupportable. – Jonathan Leffler Aug 13 '19 at 16:08
  • 1
    @J.Panek: some variants of `signal()` reset the signal handler and require the signal handler to reinstate signal handling. Look up `SA_RESETHAND` in [`sigaction()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html) which is there in large part to allow the simulation of antique `signal()` implementations using `sigaction()`. The semantics of `signal()` in standard C are minimal; the semantics of `signal()` in POSIX reflect the divergence of actual implementations before POSIX was standardized. POSIX recommends `sigaction()` for many reaons. – Jonathan Leffler Aug 13 '19 at 16:12
  • @J.Panek even after doing these removing everything from a signal handler except printf, I still get the output as shown in code 1. I want some way to start the loop from the start if interrupt happens in the middle of the execution of the loop. – Mandar Sadye Aug 13 '19 at 16:15
  • 1
    When you have the `loop()` call in the signal handler, you'd probably find that the the interrupt signal is still blocked because you have not returned from the original signal handler. Also, calling `loop()` in the signal handler means you're calling `loop()` recursively (albeit indirectly recursively). The original call to `loop()` hasn't finished either. Your `loop()` does no error checking; I hope that's because you've created an MCVE ([Minimal, Complete, Verifiable Example?](https://stackoverflow.com/help/mcve)) (or MRE or whatever name SO now uses). – Jonathan Leffler Aug 13 '19 at 16:15
  • 1
    You should be paying attention to the return value from `scanf()`. It will probably return an error condition if it is interrupted — and if it is interrupted, you should not use the value in `a`. – Jonathan Leffler Aug 13 '19 at 16:17
  • @JonathanLeffler I will definitely look into the link you added in comments and consider switching to `sigaction`. What I want to know is that is there a way to start a loop cycle from the beginning if I am interrupted in the middle of executing a loop cycle. So, actually, I don't want to use the value from `scanf` but ask the user again. is there any way to change the flow of program on interrupt. Is it a good idea to use goto? For example, if `bool inLoop` is true the goto first statement in the loop. – Mandar Sadye Aug 13 '19 at 16:25
  • 2
    Look at what `scanf()` returns. If it says `1`, you're OK to use the contents of `a`. If it says `0` or `EOF`, you have more work to do. You may need to use `clearerr(stdin)` to get anything sensible from the terminal thereafter. You can then go back and prompt again. – Jonathan Leffler Aug 13 '19 at 16:30

1 Answers1

2

Jonathan Leffler provided helpful hints (though not sufficient, at least on some popular operating system):

  • Look at what scanf() returns.
  • Don't use loop() in the signal handler
  • Don't use signal() — use sigaction(). In your case, this has two advantages:
    • Restoring the signal action to the default state on call of the signal handler can be avoided, so you don't have to change the action again in the handler.
    • Restarting the read() system call (inside scanf()) can be avoided, so that scanf() returns and you can react to the interrupt in the first place.

So, the signal handler can be just

void sigintHandler(int sig_num)
{
    printf("\n");   // or perhaps better write(1, "\n", 1)
}

The signal(SIGINT, sigintHandler); in main() can be replaced with

  sigaction(SIGINT, &(struct sigaction){ .sa_handler = sigintHandler }, NULL);

and the scanf("%s",a); with

      if (scanf("%s", a) == EOF)
      {
          if (errno == EINTR) continue; // read operation interrupted by signal
          return;
      }

EOF is returned on interrupt as well as on end of file, so the cases are to be distinguished through errno. Besides, it's nice if your program provides a way to exit it, thus the return.

Armali
  • 18,255
  • 14
  • 57
  • 171
  • 1
    Well, what I was missing was the check of `errno`. Good thing is that it works for `getc` in my original code. I will definitely keep in mind your suggestions. Thank you. – Mandar Sadye Aug 14 '19 at 16:55