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

4

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);
Bract answered 28/1, 2013 at 23:13 Comment(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.Hannover
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.Polycrates
P
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.

Polycrates answered 28/1, 2013 at 23:22 Comment(2)
Yes, thank you. I have started to dig self-pipe trick. That seems far better.Bract
Only the second option works for signals like SIGSEGV, SIGILL, etc. The self-pipe trick won't work in those cases either.Racklin
D
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://mcmap.net/q/56332/-how-can-i-convert-an-int-to-a-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: https://mcmap.net/q/55313/-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.

Drogheda answered 31/8, 2018 at 8:28 Comment(5)
This program does not work as advertised. Program returns without the ability to press control-c.Acclaim
@Acclaim thanks for feedback. I tested again on Ubuntu 18.04 and it worked again. Please provide your distro / GCC version.Drogheda
gcc version 8.2.1 20181127 (GCC) and Linux unknown 4.19.8-arch1-1-ARCH #1 SMP PREEMPT Sat Dec 8 13:49:11 UTC 2018 x86_64 GNU/Linux. When I run it, the command prompt returns... promptly!Acclaim
@Acclaim thanks. And what is your distro? Ubuntu 18.04 here.Drogheda
4.19.8-arch1-1-ARCH Which is Arch Linux 4.19.8Acclaim
K
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
Kosaka answered 13/6, 2022 at 14:48 Comment(0)
O
-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;
}
Oleaster answered 28/1, 2013 at 23:35 Comment(10)
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.Polycrates
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.Polycrates
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.Oleaster
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.Oleaster
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.Polycrates
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.Oleaster
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.Polycrates
@R.. according to this: 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 ...Oleaster
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.Oleaster
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.Polycrates

© 2022 - 2024 — McMap. All rights reserved.