23

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);
chema989
  • 3,962
  • 2
  • 20
  • 33
  • 2
    If your program is even slightly non-trivial, it might be a lot simpler to set up an `signalfd` and slot that into your event loop. Then you can do anything you like in response to the signal. – Kerrek SB Jan 28 '13 at 23:15
  • 2
    `signalfd` is not portable; it's Linux-specific. However, there's been a portable version of the same thing for pretty much the entire history of unix: [the self-pipe trick](http://cr.yp.to/docs/selfpipe.html). – R.. GitHub STOP HELPING ICE Jan 28 '13 at 23:19

4 Answers4

11

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.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Yes, thank you. I have started to dig self-pipe trick. That seems far better. –  Jan 28 '13 at 23:24
  • 2
    Only the second option works for signals like SIGSEGV, SIGILL, etc. The self-pipe trick won't work in those cases either. – Paul Coccoli Jul 17 '15 at 13:39
4

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.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
0

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
Seirios
  • 107
  • 7
-1

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;
}
wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • 4
    Unfortunately, `vsnprintf` is not required to be async-signal-safe either. You would think this would be just a theoretical issue that doesn't matter in real-world implementations, but glibc's implementation is definitely **not** async-signal-safe as it uses `malloc`. – R.. GitHub STOP HELPING ICE Jan 29 '13 at 00:04
  • 2
    Also, `fileno` is not async-signal-safe. It is required to obtain a lock on the file, and could mess up badly if the interrupted code was in the middle of locking/unlocking the file. Fortunately, your use of `fileno` is bogus and unnecessary. `stderr` is file descriptor number 2. If you prefer not to use "magic numbers", `STDERR_FILENO` is available. – R.. GitHub STOP HELPING ICE Jan 29 '13 at 00:05
  • 1
    Ah, I stand corrected then. Maybe then create your own (async safe...) snprintf() function. (last time I checked apache's implementation was 50KLOC ...) BTW: I knew about STDERR_FILENO; it was just cargo-cult, programming, I presume) Basically, there is only the self-pipe trick that remains. – wildplasser Jan 29 '13 at 00:06
  • BTW: could there pe a solution of shoving the entire va_arg *contents* into a pipe (upto PIPE_BUFF, obviously) ? Its would require a deep copy, and the "arguments" may or may not be valid anymore at the time of processing, I realise that. – wildplasser Jan 29 '13 at 00:15
  • I don't think there's any way to make that work, since C provides no way to "construct" a new `va_list` programmatically. The canonical way to "emulate" async-signal-safe `printf` on top of a non-safe one would be to create a dedicated thread in advance that's responsible for performing `printf` requests. The signal handler would pass a pointer to a control structure containing the `va_list` pointer over a self-pipe, and would wait on another self-pipe to synchronize completion of the operation. – R.. GitHub STOP HELPING ICE Jan 29 '13 at 00:25
  • And: as you said, the recieving end woud have to construct a va_arg struct.You mean, the "deep copy" is not possible (and: certainly not portable). It was a wild idea, anyway. (a "deep copy" would only involve a "minor inspection" of the format string, ignoring all the modifyers. But maybe still too heavy for use inside a signal handler) And (as you said) the "recieving end" would need to construct a va_args thingy. – wildplasser Jan 29 '13 at 00:29
  • Actually, walking the argument list based on the format string is possible, albeit delicate. What's not possible is making a new `va_list` based on it to pass to `vsnprintf` or `vfprintf` later, simply because there's no programmatic way to make a `va_list`. – R.. GitHub STOP HELPING ICE Jan 29 '13 at 00:31
  • @R.. according to this: http://www-it.desy.de/cgi-bin/man-cgi?vsnprintf+3 vsnprintf() et.al. *are* async-safe. (the code example is terrible, though) It is gets confusing, I'm going to check the source ... – wildplasser Apr 30 '13 at 10:02
  • Yup, I got the feeling that it was a BSDism. Still need to check what GNU libc does. I remember cases (DEC OSF before 5.1 IIRC), where vsnprintf() was present in the library, but not exposed via a public header file). Before "standardisation" the return value could also be -1 on error (instead of >= 2nd argument) I still have the C compiler notes at work, I'll see tomorrow if the async-safe semantics were included there. – wildplasser Apr 30 '13 at 14:36
  • In glibc, the printf core code can call `malloc`, so there's no way it could be async-signal-safe. There may be some code paths where you can rely on it not calling `malloc` (depending on which formats are used, etc.) but unless this is documented, it could very well change from version to version, so it would be unsafe to rely on it. – R.. GitHub STOP HELPING ICE Apr 30 '13 at 15:42