1

Tried my best to figure this out on my own, but I really do not want to continue tampering with things that I do not fully understand. So for a programming assignment I have to do in C, I need to terminate a program upon the user entering CTRL+D key stroke via a terminal. I tried to isolate that functionality in a smaller test function, but now my CTRL+D behaves as my CTRL+C and CTRL+C does not have any effect, even outside of the program when it finishes executing. This is the program that caused this change:

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

void ctrlD(int sig){ 
  printf("\n");
  signal(SIGINT, SIG_DFL);
  exit(0); 
}

int main(){
  signal(SIGINT, ctrlD);
  while(1) {
    printf("Hello\n");
    sleep(5);
  }
}

The line signal(SIGINT, SIG_DFL); was added afterward upon realizing my CTRL+C no longer worked. I thought it would return the keystrokes to their original functionalities, but to no avail. What do I do to get back the original functionalities while also making this program work with CTRL+D?

***EDIT: This question seems to have gone off the rails a bit. I get now that Ctrl+D is not a signal. Nonetheless, I no longer have the functionality of Ctrl+C anymore when attempting to use it in my MAC OS terminal, and instead Ctrl+D seems to have that exact functionality. HOW exactly can I return each to have the functionality that they had before I went on this haphazard journey?

Developer Guy
  • 2,318
  • 6
  • 19
  • 37
jmglynn
  • 31
  • 1
  • 4
  • 1
    `exit(0);` in the handler caused the process to terminate there itself, remove it if not required. – Achal Apr 18 '18 at 04:16
  • The Linux and FreeBSD projects have expended a great amount of effort towards documenting many standard platform calls. I would recommend you check out their `man` pages. `man 2 signal` https://www.systutorials.com/docs/linux/man/2-signal/ – Mark Apr 18 '18 at 04:17
  • 1
    ctrl + D is not SIGINT, SIGINT is Ctrl +c. – Monk Apr 18 '18 at 04:59
  • 2
    By default Ctrl+D is an end-of-file indicator when reading from terminal. It has nothing to do with signals. – n. m. could be an AI Apr 18 '18 at 05:44
  • If you want to change the interrupt character, use termios, not signals. – rici Apr 18 '18 at 07:12

4 Answers4

3

If your intention is to restore signal's default behavior after executing handler then, pass SA_RESETHAND flag to sa_flags while registering signal action. For example.

struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_RESETHAND;
act.sa_handler = some_handler;
sigaction(SIGINT, &act, NULL);

From sigaction() man

SA_RESETHAND

Restore the signal action to the default upon entry to the signal handler. This flag is meaningful only when establishing a signal handler.

Monk
  • 756
  • 5
  • 16
  • 1
    Your answer could be improved by also setting `sa_handler` or `sa_sigaction` in the `struct sigaction` you're using. – Andrew Henle Apr 18 '18 at 10:36
0

Its because of exit(0) statement in the handler, when SIGINT is raised, handler strlD gets called and you might thinking why signal(SIGINT,SIG_DFL) didn't work ? Actually it works. But your main process a.out get terminated successfully there itself by calling exit(0). remove exit(0) if you want to restore the behavior of SIGINT.

#include <unistd.h> 
#include <stdio.h> 
#include <termios.h> 
#include <signal.h> 
#include <stdlib.h>
void ctrlD(int sig){ 
        //printf("CTRL+C pressed\n");/* just to observe I added one printf
                   statement, Ideally there shouldn't be any printf here */
        signal(SIGINT, SIG_DFL);/*restoring back to original action */
}
int main(){
        signal(SIGINT, ctrlD);/*1st time when CTRL+C pressed, handler ctrlD gets called */
        while(1) {
                printf("Hello\n");
                sleep(5);
        }
        return 0;
}

Also its advisable to use sigaction() instead of signal() as told here What is the difference between sigaction and signal? . Read man 2 sigaction and man 2 exit to check what exit(0) means.

Also this How to avoid using printf in a signal handler?

Edit :

void ctrlD(int sig){
        /* printf("CTRL+C pressed \n"); */
        signal(SIGINT, SIG_DFL); /* only one time CTRL+C works
                               after that SIG_DFL will terminate whole process */
}
int main(){
        signal(SIGINT, ctrlD); /* if you press CTRL+C then it will go to handler
                                and terminate */
        int ch;
        while( ((ch = getchar())!=EOF) ) { /* wait or read char until CTrl+D is not pressed  */
                printf("Hello : %d \n",ch);/* ASCII equivalent of char */
        }
        return 0;
}
Achal
  • 11,821
  • 2
  • 15
  • 37
  • I tried your proposed solution, but now all that has changed is that it takes two attempts of CTRL+D to exit the program, and CTRL+C still has no functionality, even outside of the program. – jmglynn Apr 18 '18 at 04:34
  • The Handler is set for signal no `2` or `SIGINT` i.e when you pressed `CTRL+C` key, it goes to `ctrlD` function & there if you press once again `CTRL+C` it should terminate and its terminating because default action(`SIG_DFL`) of SIGINT is terminate the process, as you can see in `man 7 signal` . Which part you didn't get ? – Achal Apr 18 '18 at 04:42
  • I added one statement in `ctrlD` function, check how many times that `printf` statement gets printed. – Achal Apr 18 '18 at 04:47
  • This doesn't seem to be doing the trick. Upon hitting CTRL+C, it simply prints ^C among the lines of Hello, and the CTRL+D needs to be pressed twice to stop the program. Then upon calling other programs I have written, I must use CTRL+D to terminate them while CTRL+C has no effects. I'm sorry if I'm missing something here, but I just want to be sure you/others know precisely what is happening on my end. – jmglynn Apr 18 '18 at 04:55
  • _I must use `CTRL+D` to terminate them ?_ `CTRl+D` is not any `signal` & you set the handler for `CTRl+C` not for `CTRL+D` – Achal Apr 18 '18 at 04:58
  • pressing `CRL+D` means you are indicating its `EOF(End of File)`, its not any signal. Run `kill -l` on command prompt, it will list all possible signals. – Achal Apr 18 '18 at 05:01
  • Yes, that is correct. The assignment ask that upon user typing CTRL+D, the program will terminate. How do I go about approaching this? And how can I restore my CTRL+C to the way it should be? – jmglynn Apr 18 '18 at 05:01
  • your second question _how can I restore my CTRL+C to the way it should be?_ I answered above already. About your first question, _until `CTRL+D` is not pressed what you want to do ?_ – Achal Apr 18 '18 at 05:10
  • The answer you've provided is not working for me. Both with this program specifically and outside of it, CTRL+C no longer works and I am unsure why. I must use CTRL+D to terminate any of my programs. – jmglynn Apr 18 '18 at 05:13
  • I edited my answer regarding your last point @jmglynn It might helps you, read the comments in the code properly. – Achal Apr 18 '18 at 05:20
  • `getchar()` returns `int` (not `char`) by intention! – alk Apr 18 '18 at 06:15
  • As you now changed `ch` to be an `int`, you need to also adjust the call to `printf()` `ch`. – alk Apr 18 '18 at 06:18
  • ohh.. my bad @alk – Achal Apr 18 '18 at 06:22
0

If you write a program to explore signals, it is much better to write it carefully, using proper POSIX interfaces (sigaction() instead of signal()), and avoiding undefined behaviour (using non-async-signal safe functions in a signal handler).

Consider, for example, the following program:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>

static volatile sig_atomic_t  sigint_count = 0;

static void catch_sigint(int signum)
{
    if (signum == SIGINT)
        sigint_count++;
}

static int install_sigint(void)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);

    act.sa_handler = catch_sigint;
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) == -1)
        return errno;

    return 0;
}

static int install_default(const int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);

    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;

    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

int main(void)
{
    struct timespec  duration;
    int              result;

    if (install_sigint()) {
        fprintf(stderr, "Cannot install SIGINT handler: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    duration.tv_sec = 5;
    duration.tv_nsec = 0; /* 1/1000000000ths of a second. Nine zeroes. */

    printf("Sleeping for %d seconds.\n", (int)duration.tv_sec);
    fflush(stdout);
    while (1) {

        result = nanosleep(&duration, &duration);
        if (!result)
            break;

        if (errno != EINTR) {
            fprintf(stderr, "nanosleep() failed: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }

        /* nanosleep was interrupted by a delivery of a signal. */

        if (sigint_count >= 3) {
            /* Ctrl+C pressed three or more times. */

            if (install_default(SIGINT) == -1) {
                fprintf(stderr, "Cannot revert SIGINT to the default handler: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            }

            printf("SIGINT has been reverted to the default handler.\n");
            fflush(stderr);
        }
    }

    if (sigint_count > 0)
        printf("You pressed Ctrl+C %d time%s.\n", (int)sigint_count, (sigint_count > 1) ? "s" : "");
    else
        printf("You did not press Ctrl+C at all.\n");

    return EXIT_SUCCESS;
}

The #define tells your C library (glibc in particular) that you want POSIX.1-2008 (and later) features from it.

The INT signal handler only increments a volatile sig_atomic_t counter. Note that this type may have a very small range it can represent; 0 to 127, inclusive, should be safe.

The main program waits using the POSIX nanosleep() function. On some systems, sleep() may be implemented via the SIGALRM function, so it is better avoided when using signals otherwise; nanosleep() does not interfere with signals like that at all. Plus, nanosleep() can return the amount of time remaining, if it is interrupted by a signal delivery.

In the main loop, nanosleep() will return 0, if it has slept the entire interval (but note that it may not update the remaining time to 0 in this case). If it is interrupted by the delivery of a signal, it will return -1 with errno == EINTR, and the remaining time updated. (The first pointer is to the duration of the sleep, and the second is to where the remaining time should be stored. You can use the same structure for both.)

Normally, the main loop does only one iteration. It can do more than one iteration, if it is interrupted by the delivery of a signal.

When the main loop detects that sigint_count is at least three, i.e. it has received at least three INT signals, it resets the signal handler back to default.

(Note that both the memset() and the sigemptyset() are important when clearing the struct sigaction structure. The memset() ensures that future code is backwards compatible with older code, by ensuring even padding fields are cleared. And sigemptyset() is the safe way to clear the signal mask (set of signals blocked while the handler runs).)

(In theory, memset() is not async-signal-safe, while both sigemptyset() and sigaction() are. This is why I reset the signal handler in the main program, and not in the signal handler.)

If you want to print from a signal handler, you need to use low-level I/O, because <stdio.h> functions are not async-signal safe. For example, you can use the following function to print strings to standard output:

static int wrerr(const char *p)
{
    const int  saved_errno = errno;
    int        retval = 0;

    if (p) {
        const char *q = p;
        ssize_t     n;

        while (*q)
            q++;

        while (p < q) {
            n = write(STDERR_FILENO, p, (size_t)(q - p));
            if (n > 0)
                p += n;
            else
            if (n != -1) {
                retval = EIO;
                break;
            } else
            if (errno != EINTR) {
                retval = errno;
                break;
            }
        }
    }

    errno = saved_errno;
    return retval;
}

The above wrerr() function is async-signal safe (because it only uses async-signal safe functions itself), and it even keeps errno unchanged. (Many guides forget to mention that it is quite important for a signal handler to keep errno unchanged. Otherwise, when a function is interrupted by a signal handler, and that signal handler modifies errno, the original function will return -1 to indicate an error, but then errno is no longer EINTR!)

You can just use wrerr("INT signal!\n") if you want. The return value from wrerr() is zero if the write was successful, and an errno error code otherwise. It ignores interrupts itself.

Do note that you should not mix stderr output via fprintf() or other <stdio.h> functions with the above (except perhaps for printing error messages when the program aborts). Mixing them is not undefined behaviour, it just may yield surprising results, like wrerr() output appearing in the midst of a fprintf(stderr,...) output.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
0

Thank you everyone who contributed to this question. The resources provided/linked were tremendously helpful in learning more about signals (and that EOF isn't a signal), among the other wealth of information provided.

After some more research, I found out that somehow, either through some accidental bash command gone awry, or perhaps the program posted in my original question itself, I had altered the key mappings for my terminal's stty settings. If anyone finds themselves in this oddly specific situation in the future, I hope this can be of help, as it is what fixed my problem:

Enter the command $ stty -a to see all of your terminals settings, specifically the "cchars" section.

I then saw the reversal, and fixed it like so:

$ stty intr ^C
$ stty eof ^D

Then you can run $ stty -a once again to see that the changes have properly taken effect. Once again, thanks everyone.

jmglynn
  • 31
  • 1
  • 4