Search code examples
csignals

Client/Server communication in C with system signals: client not processing signal from server and crashing


I am building a client server communication project. The client encodes a char * string into bits and sends it to the server. 0s are sent to the server with SIGUSR1 and 1s are sent to the server using SIGUSR2

Each time server receives a signals, it sends back SIGUSR1 to client. Once everything is received (\0), server prints message and sends SIGUSR2 to the client.

Sometimes, when running the code, client does not process the signal and I have this error message in the terminal:

c4c4c2% ./client 46339 test
zsh: user-defined signal 1  ./client 46339 test

The signal not being treated, process interrupts and project stops working.

Do you know why this is happening?

CLIENT CODE

#include "minitalk.h"

volatile sig_atomic_t   g_ack_received = 0;

/*Waits for a acknowledgment rom the server before processing the next signal.
If no acknowledgment has been received after one second, '\1' is sent to the
server so that server resources are properly freed and the server can receive a
new message*/
void    wait_for_server_ack(int pid, int delay)
{
    int timeout;
    int i;

    timeout = 0;
    while (!g_ack_received)
    {
        usleep(delay);
        if (++timeout > 10 * delay)
        {
            i = 0;
            while (i--)
            {
                if ('\1' >> i & 1)
                    send_signal(pid, SIGUSR2);
                else
                    send_signal(pid, SIGUSR1);
                usleep(delay);
            }
            exit(ft_printf_colour(RED_BOLD, TIME_OUT));
        }
    }
}

/*Checks that the arguments of the program are valid:
• Two arguments to the 'client' program
• First argument is numeric (PID of the 'server')
• First argument is not a protected process (PID < 1050)
• Second argument is a not empty string*/
t_bool  argument_is_valid(int argc, char **argv)
{
    const char  *error_msg = NULL;

    if (argc != 3)
        error_msg = ERR_ARG_NR;
    else if (!ft_isnumeric(argv[1]))
        error_msg = ERR_NON_NUM_PID;
    else if (ft_atoi(argv[1]) < 1050)
        error_msg = PROTECTED_PID;
    else if (!ft_strlen(argv[2]))
        error_msg = ERR_EMPT_STR;
    if (error_msg)
    {
        ft_printf_colour(RED_BOLD, error_msg);
        return (FALSE);
    }
    return (TRUE);
}

/*Sends a bit encoded message to the server whose PID is 'pid' with SIGUSR1 to
represent 0 and SIGUSR2 to represent 1
Once the message is sent, 11111111 (bit representation of the NULL terminator) is
sent to the server to indicate message is over*/
void    send_message(int pid, char *str)
{
    int     i;
    char    c;

    while (*str)
    {
        i = 8;
        c = *str++;
        while (i--)
        {
            g_ack_received = 0;
            if (c >> i & 1)
                send_signal(pid, SIGUSR2);
            else
                send_signal(pid, SIGUSR1);
            wait_for_server_ack(pid, 500);
        }
    }
    i = 8;
    while (i--)
    {
        g_ack_received = 0;
        send_signal(pid, SIGUSR1);
        wait_for_server_ack(pid, 500);
    }
}

/*Displays a message from the client side to assess that the server did
receive the message properly*/
void    handle_sigusr_client(int signum)
{
    static int  bit_count = 0;

    if (signum == SIGUSR1)
    {
        bit_count++;
        g_ack_received = 1;
    }
    if (signum == SIGUSR2)
        ft_printf_colour(GREEN_LIGHT,
            "Done, %d characters received by server", bit_count / 8);
}

/*Checks that the program arguments are valids and sends message to the server.
Expects a signal from the server once the message has been received*/
int main(int argc, char **argv)
{
    struct sigaction    sa;

    if (!argument_is_valid(argc, argv))
        return (1);
    sa.sa_handler = handle_sigusr_client;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGUSR1, &sa, NULL) == -1
        || sigaction(SIGUSR2, &sa, NULL) == -1)
    {
        ft_printf(RED_BOLD, ERR_SIGAC);
        return (1);
    }
    send_message(ft_atoi(argv[1]), argv[2]);
    return (0);
}

SERVER CODE

#include "minitalk.h"

/*Checks that the message buffer can be freed, frees it and sets it to NULL*/
int free_resources(char **message)
{
    if (*message)
    {
        free(*message);
        *message = NULL;
    }
    return (0);
}

/*Adds the character 'c' at the end of the string pointed by 'str'. If 'str' is
not big enough to receive the new character, memory is reallocated to increase
the capacity of 'str' by BLOCK_SIZE
add_char_to_str ensures that 'str' is NULL terminated*/
void    add_char_to_str(char c, char **str)
{
    static int  capacity = BLOCK_SIZE;
    static int  size = 0;
    char        *new_str;

    if (!(*str))
    {
        capacity = BLOCK_SIZE;
        size = 0;
        *str = (char *)malloc(capacity * sizeof(char));
        if (!(*str))
            exit(ft_printf_colour(RED_BOLD, "%s", ERR_MALLOC));
    }
    if (size + 2 > capacity)
    {
        capacity += BLOCK_SIZE;
        new_str = (char *)malloc(capacity * sizeof(char));
        if (!new_str)
            exit(ft_printf_colour(RED_BOLD, "%s", ERR_MALLOC));
        ft_memmove(new_str, *str, size);
        free(*str);
        *str = new_str;
    }
    (*str)[size] = c;
    (*str)[++size] = '\0';
}

/*Functions checks that only SIGUSR1 and SIGUSR2 are processed by the server.
It accumulates bits received by the client in a buffer int before storing each
byte in a static char * 'message'
Once a NULL terninator is received by the client, 'message' is displayed on the
standard output and memory is properly freed*/
void    handle_sigusr_server(int signum, siginfo_t *info, void *context)
{
    static int  buffer = 0;
    static int  bits_received = 0;
    static char *message = NULL;

    (void)context;
    if (signum == SIGINT)
        exit(free_resources(&message));
    buffer = (buffer << 1 | (signum == SIGUSR2));
    if (++bits_received == 8)
    {
        if ((char)buffer == '\1')
            free_resources(&message);
        else if ((char)buffer == '\0')
        {
            ft_printf("%s\n", message);
            free_resources(&message);
            send_signal(info->si_pid, SIGUSR2);
        }
        else
            add_char_to_str((char)buffer, &message);
        buffer = 0;
        bits_received = 0;
    }
    send_signal(info->si_pid, SIGUSR1);
}

/*Displays the PID of the server once it is launched and then waits for SIGUSR1
and SIGUSR2 from the client to display the encoded message*/
int main(void)
{
    struct sigaction    sa;

    sa.sa_handler = 0;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handle_sigusr_server;
    sigemptyset(&sa.sa_mask);
    ft_printf_colour(YELLOW_BOLD, "Server PID: %i\n\n", getpid());
    if (sigaction(SIGINT, &sa, NULL) == -1
        || sigaction(SIGUSR1, &sa, NULL) == -1
        || sigaction(SIGUSR2, &sa, NULL) == -1)
    {
        ft_printf(RED_BOLD, ERR_SIGAC);
        return (1);
    }
    while (1)
        pause();
}

COMMON CODE

#include "minitalk.h"

/*Sends the signal 'signu;' to the process whith ID 'PID' and prints the errno
and exits the process in case of failure of kill() function*/
void    send_signal(pid_t pid, int signum)
{
    if (kill(pid, signum) == -1)
    {
        ft_printf_colour(RED_BOLD, KILL_FAIL, errno);
        exit (EXIT_FAILURE);
    }
}

I tried to add more time in my usleep() functions both on client and server side but it did not prevent this bug from happening

Each time a bug occurs, buffer on server side still has some content so that the next call of ./client does not work and some non printable characters are being printed


Solution

  • Be careful with anything you do inside a signal handler. It can interrupt your thread of execution at any time and any instruction. When calling any function inside the signal handler and outside, you have to be sure that this function is reentrant. The main program could have entered it before the signal handler. If it does mangle with some global state or possibly hold locks, you are in trouble.

    If this is a Linux-specific solution, I would advise you to write it using signalfd. You can receive signals safely and synchronously via such a file descriptor.

    Alternatively, POSIX provides sigtimedwait and sigwaitinfo for synchronous signal delivery.