ollien
ollien

Reputation: 4786

sigaction passes SIGINT to system call, but not signal

I have a loop handling an accept(2) call. I want to be able to perform some cleanup when a SIGINT is sent to the program. My first thought was to use the signal function.

void signal_handler(int signal) {
    printf("Caught signal\n");
}

int main() {
    signal(SIGINT, &signal_handler);
    // ...
    int accept_fd = accept(sock, NULL, NULL);
    if (accept_fd == -1) {
        close(sock);
        perror("accept");
        return 1;
    }
    // ...
}

However, this simply prints "Caught signal" and then the program continues on.

If I modify main to use sigaction, the program works as expected.

int main() {
    struct sigaction sa;
    sa.sa_handler = &signal_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    // ...
    int accept_fd = accept(sock, NULL, NULL);
    if (accept_fd == -1) {
        close(sock);
        perror("accept");
        return 1;
    }
    // ...
}

Upon sending SIGINT, I get Caught Signal, followed by accept: Interrupted system call. From the man page for accept(2)

ERRORS

...

EINTR The system call was interrupted by a signal that was caught before a valid connection arrived; see signal(7).

I understand that sigaction is more modern and that I should be using it over signal, but I'm quite curious why it provides this difference in functionality.

Below I've included a full usable example program for each case.

Example with signal(2)

#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BUFFER_SIZE 32

void signal_handler(int signal) {
    printf("Caught signal\n");
}

int main() {
    signal(SIGINT, &signal_handler);
    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    struct addrinfo *addr_info;
    int info_result = getaddrinfo("localhost", "8080", &hints, &addr_info);
    if (info_result != 0) {
        printf("Getting address failed\n");
        return 1;
    }
    int sock = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
    if (sock == -1) {
        printf("Socket Failed\n");
        return 1;
    }
    int bind_result = bind(sock, addr_info->ai_addr, addr_info->ai_addrlen);
    if (bind_result == -1) {
        close(sock);
        printf("Bind Failed\n");
        return 1;
    }
    int listen_result = listen(sock, 8);
    if (listen_result == -1) {
        close(sock);
        printf("Listen Failed\n");
        return 1;
    }
    printf("Waiting...\n");
    int accept_fd = accept(sock, NULL, NULL);
    if (accept_fd == -1) {
        close(sock);
        perror("accept");
        return 1;
    }
    printf("Got fd %d\n", accept_fd);
    char *buffer = malloc(BUFFER_SIZE * sizeof(char));
    int n;
    while ((n = read(accept_fd, buffer, BUFFER_SIZE)) > 0) {
        printf("%.*s\n", n, buffer);
    }
    close(sock);
}

Example with sigaction(2)

#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BUFFER_SIZE 32

void signal_handler(int signal) {
    printf("Caught signal\n");
}

int main() {
    struct sigaction sa;
    sa.sa_handler = &signal_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    struct addrinfo *addr_info;
    int info_result = getaddrinfo("localhost", "8080", &hints, &addr_info);
    if (info_result != 0) {
        printf("Getting address failed\n");
        return 1;
    }
    int sock = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
    if (sock == -1) {
        printf("Socket Failed\n");
        return 1;
    }
    int bind_result = bind(sock, addr_info->ai_addr, addr_info->ai_addrlen);
    if (bind_result == -1) {
        close(sock);
        printf("Bind Failed\n");
        return 1;
    }
    int listen_result = listen(sock, 8);
    if (listen_result == -1) {
        close(sock);
        printf("Listen Failed\n");
        return 1;
    }
    printf("Waiting...\n");
    int accept_fd = accept(sock, NULL, NULL);
    if (accept_fd == -1) {
        close(sock);
        perror("accept");
        return 1;
    }
    printf("Got fd %d\n", accept_fd);
    char *buffer = malloc(BUFFER_SIZE * sizeof(char));
    int n;
    while ((n = read(accept_fd, buffer, BUFFER_SIZE)) > 0) {
        printf("%.*s\n", n, buffer);
    }
    close(sock);
}

Upvotes: 3

Views: 779

Answers (1)

John Zwinck
John Zwinck

Reputation: 249582

On BSD and Linux, signal() is equivalent to sigaction() with sa_flags set to SA_RESTART. If you set that flag in your sigaction() code, it will behave the same as your signal() code. If that's not what you want, then you must only use sigaction().

Notes from the Linux man page (which also apply to BSD and OS X):

On BSD, when a signal handler is invoked, the signal disposition is not reset, and further instances of the signal are blocked from being delivered while the handler is executing. Furthermore, certain blocking system calls are automatically restarted if interrupted by a signal handler (see signal(7)). The BSD semantics are equivalent to calling sigaction(2) with the following flags:

       sa.sa_flags = SA_RESTART;

Upvotes: 3

Related Questions