Search code examples
csignals

Can I close a file descriptor inside a signal handler?


currently in my classes we were learning how we can handle signals in C. I found it very interesting so I was messing around with it to try and learn it nicely.
Here's the problem: I wanted to make a program that reads from stdin on an infinite loop and writes whatever is inputed in to a file. When I want it to stop I would just use CTRL + C and terminate the program.
I wanted to mess around with the signal function to make this terminate more gracefully, however it doesn't work. Can anyone tell me how to get the desired behavior?
This is my code:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void sigint_handler (int signum) {
    printf("\nTerminating program...\n");
    close(fd);
    _exit(0);
}

int main (int argc, char *argv[]) {
    signal(SIGINT, sigint_handler);
    int fd;
    if ((fd = open("client", O_WRONLY | O_CREAT)) == -1) {
        perror("Error opening file");
        return 1;
    }
    int len = 0;
    char buffer[1024];
    while ((len = read(STDIN_FILENO, buffer, 1024)) > 0) {
        write(fd, buffer, len);
    }
    printf("Terminated\n");
    close(fd);
    return 0;
}

The point was to inside the sigint_handler() function, it would close the file descriptor and the terminate the program.
However, I can't do this. Is there a better way to get the desired result?
Or is it okay to terminate the program without closing the file descriptor? If anyone can help me understand this better I would greatly appreciate it.


Solution

  • #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    #define TERMINATING "\nTerminating program...\n"
    
    void sigint_handler (int signum) {
        // you can use write to avoid issues raised by @Shawn
        write(2, TERMIATING, sizeof(TERMINATING));
        _exit(0); /* close is not needed _exit guarantees the close */
    }
    
    int main (int argc, char *argv[]) 
    {
        int fd;
        if ((fd = open("client", O_WRONLY | O_CREAT)) == -1) {
            perror("Error opening file");
            return 1;
        }
    
        // install signal handler ONLY AFTER successfully opening fd
        signal(SIGINT, sigint_handler);
    
        int len = 0;
        char buffer[1024];
        while ((len = read(STDIN_FILENO, buffer, 1024)) > 0) {
            write(fd, buffer, len);
        }
        // print messages like this to stderr 
        fprintf(stderr, "Terminated\n");
        close(fd); // superfluous because you are about exit
        return 0;
    }
    

    Better way to do the handler would be to keep fd local to main however to use exit(0) instead of _exit because exit(0) will cause the kernel to close any files that are open.

    What I meant to say was (thank you @John Gordon) is that it's better to use exit(0) to ensure that any exit handlers are also called as needed.