Error condition propagation in the Linux kernel source

At the request of Philip Fry on a comment posted by me on David Zeuthen's blog post “Writing a C library, part 3”, I am posting a short overview of error conveyance in the Linux kernel (source).

David Zeuthen wrote in his blog post about the integer variable errno: “For simple libraries just using libc's errno is often [the] simplest approach to handling run-time errors (since it's thread-safe and every C programmer knows it)”.

A global variable — is it simple? Yes, I could agree on that. Thread-safe? Yes, contemporary implementations of C-derived environments that support threads, in other words, POSIX, have redefined errno such that it has a per-thread-specific location. It may look something like:

extern int *errno_location(void);
#define errno (*errno_location())

Whereby errno_location is a function with deeper magic that just eventually returns a pointer to a thread-unique location. The indirection via the pointer is necessary because errno is supposed to be an lvalue that can be assigned to. Due to the new macro, I am reluctant to still call it a “variable” in this context, since it is not a named variable anymore, but a dereference of a function's return value.

Anyhow, it does retain errno's properties: it is, in essence, still a “global variable”, or to be more exact, an object with static storage duration with added global scope/visibility. The pro and contra for such objects I need not repeat here.

So my replying comment to David's posting was: “It may be safe from other threads due to TLS [thread-local storage], but it is still a object with global scope, which results in having to longwindingly save and restore errno across calls to other opaque library functions. The Linux kernel in contrast shows how to do without such a global.”. An example to the necessary save-and-restore cycle is this piece of common housekeeping done just before forking:

bool all_or_nothing(int *p)
{
        unsigned int i;

        for (i = 0; i < PIPE_PAIRS * 2; ++i)
                p[i] = -1;
        if (build_pipes(p) != 0) {
                saved_errno = errno;
                kill_pipes(p); /* calls close() */
                errno = saved_errno;
                return false;
        }
        return true;
}

The handling in the error path is somewhat cumbersome, because close can set errno. This prompted me to present the case of how the Linux kernel internally conveys error conditions. One code example:

int all_or_nothing(int *p)
{
        unsigned int i;
        int ret;

        for (i = 0; i < PIPE_PAIRS * 2; ++i)
                p[i] = -1;
        ret = build_pipes(p);
        if (ret < 0)
                kill_pipes(p);
        return ret;
}

Error conveyance in the Linux kernel generally follows this abstract ruleset:

Posted 2011-07-06 07:07 / Tags: Kernel, Linux. / link