user648129
user648129

Reputation:

Print int from signal handler using write or async-safe functions

I want to print a number into log or to a terminal using write (or any async-safe function) inside a signal handler. I would prefer not to use buffered I/O.

Is there an easy and recommended way to do that ?

For example in place of printf, below I would prefer write (or any asyn safe function).

void signal_handler(int sig)
{
  pid_t pid;
  int stat;
  int old_errno = errno;

  while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
    printf("child %d terminated\n", pid);

  errno = old_errno;
  return;
}

Printing strings is easy. In place of the printf above I can use (without printing pid):

write(STDOUT_FILENO, "child terminated", 16);

Upvotes: 23

Views: 17119

Answers (4)

Seirios
Seirios

Reputation: 109

You can use string handling functions (e.g. strcat) to build the string and then write it in one go to the desired file descriptor (e.g. STDERR_FILENO for standard error).

To convert integers (up to 64-bit wide, signed or unsigned) to strings I use the following functions (C99), which support minimal formatting flags and common number bases (8, 10 and 16).

#include <stdbool.h>
#include <inttypes.h>

#define STRIMAX_LEN 21 // = ceil(log10(INTMAX_MAX)) + 2
#define STRUMAX_LEN 25 // = ceil(log8(UINTMAX_MAX)) + 3

static int strimax(intmax_t x,
                   char buf[static STRIMAX_LEN],
                   const char mode[restrict static 1]) {
    /* safe absolute value */
    uintmax_t ux = (x == INTMAX_MIN) ? (uintmax_t)INTMAX_MAX + 1
                                     : (uintmax_t)imaxabs(x);

    /* parse mode */
    bool zero_pad = false;
    bool space_sign = false;
    bool force_sign = false;
    for(const char *c = mode; '\0' != *c; ++c)
        switch(*c) {
            case '0': zero_pad = true; break;
            case '+': force_sign = true; break;
            case ' ': space_sign = true; break;
            case 'd': break; // decimal (always)
        }

    int n = 0;
    char sign = (x < 0) ? '-' : (force_sign ? '+' : ' ');
    buf[STRIMAX_LEN - ++n] = '\0'; // NUL-terminate
    do { buf[STRIMAX_LEN - ++n] = '0' + ux % 10; } while(ux /= 10);
    if(zero_pad) while(n < STRIMAX_LEN - 1) buf[STRIMAX_LEN - ++n] = '0';
    if(x < 0 || force_sign || space_sign) buf[STRIMAX_LEN - ++n] = sign;

    return STRIMAX_LEN - n;
}

static int strumax(uintmax_t ux,
                   char buf[static STRUMAX_LEN],
                   const char mode[restrict static 1]) {
    static const char lbytes[] = "0123456789abcdefx";
    static const char ubytes[] = "0123456789ABCDEFX";

    /* parse mode */
    int base = 10; // default is decimal
    int izero = 4;
    bool zero_pad = false;
    bool alternate = false;
    const char *bytes = lbytes;
    for(const char *c = mode; '\0' != *c; ++c)
        switch(*c) {
            case '#': alternate = true; if(base == 8) izero = 1; break;
            case '0': zero_pad = true; break;
            case 'd': base = 10; izero = 4; break;
            case 'o': base = 8; izero = (alternate ? 1 : 2); break;
            case 'x': base = 16; izero = 8; break;
            case 'X': base = 16; izero = 8; bytes = ubytes; break;
        }

    int n = 0;
    buf[STRUMAX_LEN - ++n] = '\0'; // NUL-terminate
    do { buf[STRUMAX_LEN - ++n] = bytes[ux % base]; } while(ux /= base);
    if(zero_pad) while(n < STRUMAX_LEN - izero) buf[STRUMAX_LEN - ++n] = '0';
    if(alternate && base == 16) {
        buf[STRUMAX_LEN - ++n] = bytes[base];
        buf[STRUMAX_LEN - ++n] = '0';
    } else if(alternate && base == 8 && '0' != buf[STRUMAX_LEN - n])
        buf[STRUMAX_LEN - ++n] = '0';

    return STRUMAX_LEN - n;
}

They can be used like this:

#include <unistd.h>

int main (void) {
    char buf[STRIMAX_LEN]; int buf_off;
    buf_off = strimax(12345,buf,"+");
    write(STDERR_FILENO,buf + buf_off,STRIMAX_LEN - buf_off);
}

that outputs:

+12345

Upvotes: 0

Implement your own async-signal-safe snprintf("%d and use write

It is not as bad as I thought, How to convert an int to string in C? has several implementations.

The POSIX program below counts to stdout the number of times it received SIGINT so far, which you can trigger with Ctrl + C.

You can exit the program with Ctrl + \ (SIGQUIT).

main.c:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * https://stackoverflow.com/questions/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: https://stackoverflow.com/questions/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char buf[ITOA_SAFE_STRLEN(sig_atomic_t)];
    enum { base = 10 };
    char *end;
    end = itoa_safe(global, buf, base);
    *end = '\n';
    write(STDOUT_FILENO, buf, end - buf + 1);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

Compile and run:

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

After pressing Ctrl + C fifteen times, the terminal shows:

^C0
^C1
^C2
^C3
^C4
^C5
^C6
^C7
^C8
^C9
^C10
^C11
^C12
^C13
^C14

Here is a related program that creates a more complex format string: How to avoid using printf in a signal handler?

Tested on Ubuntu 18.04. GitHub upstream.

Upvotes: 4

wildplasser
wildplasser

Reputation: 44250

If you insist on using xprintf() inside a signal handler you can always roll your own version that does not rely on buffered I/O:

#include <stdarg.h> /* vsnprintf() */

void myprintf(FILE *fp, char *fmt, ...)
{
char buff[512];
int rc,fd;
va_list argh;
va_start (argh, fmt);

rc = vsnprintf(buff, sizeof buff, fmt, argh);
if (rc < 0  || rc >= sizeof buff) {
        rc = sprintf(buff, "Argh!: %d:\n", rc);
        }

if (!fp) fp = stderr;
fd = fileno(fp);
if (fd < 0) return;
if (rc > 0)  write(fd, buff, rc);
return;
}

Upvotes: -1

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215287

If you really insist on doing the printing from a signal handler, you basically have 2 options:

  1. Block the signal except in a dedicated thread you create for handling the signal. This special thread can simply perform for (;;) pause(); and since pause is async-signal-safe, the signal handler is allowed to use any functions it wants; it's not restricted to only async-signal-safe functions. On the other hand, it does have to access shared resources in a thread-safe way, since you're now dealing with threads.

  2. Write your own code for converting integers to decimal strings. It's just a simple loop of using %10 and /10 to peel off the last digit and storing them to a short array.

However, I would highly recommend getting this operation out of the signal handler, using the self-pipe trick or similar.

Upvotes: 11

Related Questions