Search code examples
cexceptionioexceptionhandlersetjmp

exception handler with siglongjmp interrupting I/O


While working on the homework problem 8.25 in CSAPP, I referred to its solution available at https://dreamanddead.github.io/CSAPP-3e-Solutions/. The provided solution includes the following C code:

#include <stdio.h>
#include "csapp.h"

sigjmp_buf buf;

void handler(int sig) {
  /* jump */
  siglongjmp(buf, 1);
}

char* tfgets(char* s, int size, FILE* stream) {
  char* result;

  if (!sigsetjmp(buf, 1)) {
    alarm(5);
    if (signal(SIGALRM, handler) == SIG_ERR)
      unix_error("set alarm handler error");
    return fgets(s, size, stream);
  } else {
    /* run out of time */
    return NULL;
  }
}

The tfgets function is fgets with a timeout feature. However, a concern arises regarding the safety of using siglongjmp to jump out of IO operations.

Is it safe to use siglongjmp to interrupt IO operations and exit them abruptly?

I wasn't able to find any document or related questions on stackoverflow.


Solution

  • No! It is not safe to do siglongjmp from this handler. You can corrupt the state of the stream that fgets is operating on.

    Instead of doing sigsetjmp/siglongjmp, have the signal handler set a global (e.g. volatile int alarm_fired;).

    Then, if the handler fires, fgets should return NULL and errno should be set to EINTR.

    You can (safely) check for the return value and errno value. You can also check alarm_fired.


    However ...

    Without the siglongjmp, fgets will (under linux/glibc, at least) absorb the signal.

    And, it's unclear what errno would be set to (e.g. EINTR vs EAGAIN) even if it did return immediately.

    So, you may have to configure the stream to allow this. Some possibilities:

    1. Set the underlying file descriptor to non-blocking
    2. Use cfmakeraw
    3. Use the FIONREAD ioctl.
    4. Use select and/or poll on fileno(stream).
    5. Dispense with stream I/O and use read directly, possibly with some of the other items above.

    This seems primarily useful to get a timeout from a TTY device (e.g. stdin) or a pipe/socket rather than a file. In those cases, to me, read seems to be the better bet.


    Side note: You have a race condition.

    You set the signal handler (with signal) after doing alarm. Under heavy system loading, the signal could fire before you have a chance to set the handler.

    Put the alarm after the signal call.