En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 25 de noviembre de 2017

Errno y sus hermanos
cómo està implementado errno en C

Este post es un necesario addendum al post anterior (que si aún no lo habéis leído deberíais hacerlo en seguida, ya que están estrechamente vinculados). Esta vez hablaremos de errno y sus hermanos, una familia compleja como la de Rocco y sus hermanos, una obra maestra del neorrealismo Italiano.
Rocco Errno y sus hermanos
Vamos al grano: después de leer el último post, los lectores más atentos se habrán preguntado: "Ok, con strerror_r() podemos manejar las cadenas de error de una manera thread-safe, pero ¿de qué nos sirve si, en la base de todo, hay la variable global errno que realmente no tiene el aire de ser thread-safe?" La pregunta es legítima, y para responder tenemos que retroceder un poco en el tiempo... la historia es análoga y paralela a la de strerror() (¡solo faltaria que no!). Antiguamente errno estaba definido en el header errno.h:
    extern int errno;
y hacia referencia a una simple variable global de la libc, exactamente un int llamado errno. Luego vinieron los thread, con el estándar POSIX 1003.1c (también conocido como POSIX.1c, pero hace lo mismo), y con él vino también la strerror_r() y, como no, también errno ha conseguido un hermano thread-safe. Como bien se puede leer en el manual de errno (después de POSIX.1c) ahora errno es:
    errno is defined by the ISO C standard to be a modifiable lvalue of
    type int, and must not be explicitly declared; errno may be a macro.
    errno is thread-local; setting it in one thread does not affect its
    value in any other thread.
Entonces la nueva definición de errno ahora está en bits/errno.h (que se incluye desde el clásico errno.h). Simplificando un poco (he omitido algunos detalles para que sea más fácil de leer) la nueva impostacion es:
en el header-file errno.h
#include <bits/errno.h>
/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */
#ifndef errno
extern int errno;
#endif

en el header-file bits/errno.h
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void);

/* When using threads, errno is a per-thread value. */
#define errno (*__errno_location ())
Entonces, en pocas palabras, ahora errno ya no es un int global, sino que es "el contenido de una dirección devuelta por una función global". Obviamente, la variable int a la que apunta este objeto es el nuevo errno local de un thread (es decir, cada thread tiene su propio errno). Un ejemplo (muuuy simplificado) de cómo se podría implementar la __errno_location() es el siguiente:
// errno local de un thread: es una variable de tipo Thread-local storage (TLS)
__thread int th_errno;

int *__errno_location(void)
{
    // retorna la dirección de la variable th_errno
    return &th_errno;
}
Y, al final de todo, a pesar de los cambios descritos, aún será posible hacer operaciones como estas:
int my_errno = errno; // Ok, equivale a: int my_errno = (* __errno_location());
errno = EADDRINUSE;   // Ok, equivale a: (* __errno_location()) = EADDRINUSE;
porque, por supuesto, todo ha sido diseñado para ser retro-compatible, y por lo tanto errno, aunque ahora es una macro, todavía tiene que comportarse como si fuera un simple int.

Y, para terminar a lo grande, no podemos renunciar a un pequeño extracto del estándar POSIX.1c, que señala todo lo que se ha dicho hasta ahora:
Redefinition of errno
In POSIX.1, errno is defined as an external global variable. But this definition
is unacceptable in a multithreaded environment, because its use can result in 
nondeterministic results. The problem is that two or more threads can encounter 
errors, all causing the same errno to be set. Under these circumstances, a thread 
might end up checking errno after it has already been updated by another thread. 

To circumvent the resulting nondeterminism, POSIX.1c redefines errno as a service 
that can access the per-thread error number as follows (ISO/IEC 9945:1-1996, n2.4): 

Some functions may provide the error number in a variable accessed through the 
symbol errno. The symbol errno is defined by including the header <errno.h>, as 
specified by the C Standard ... For each thread of a process, the value of errno 
shall not be affected by function calls or assignments to errno by other threads. 

In addition, all POSIX.1c functions avoid using errno and, instead, return the 
error number directly as the function return value, with a return value of zero 
indicating that no error was detected. This strategy is, in fact, being followed 
on a POSIX-wide basis for all new functions.
Notar que la ultima parte del extracto (de In addition... en adelante) explica el porqué existe la strerror_r() XSI-compliant descrita en el post anterior: ¿Habéis visto? todo se aclara al final... Y con esto también podemos considerar resuelto el misterio del errno thread-safe. ¡Misión cumplida!

¡Hasta el próximo post!

sábado, 11 de noviembre de 2017

Strerror y sus hermanas
qué strerror escoger en C

Esta es una historia de encuentros extraños y relaciones equivocadas, como en la obra maestra (otra más) del gran Woody, "Hannah y sus hermanas". La vida a menudo nos lleva a tomar decisiones importantes, como le sucedió a Hannah, y otras un poco menos importantes como las que veremos en breve... sin embargo, siempre de decisiones se trata.
Hannah Strerror y sus hermanas
(Abro un paréntesis: en este post hablaremos de la strerror() y sus variantes. La strerror() es una función de la libc que, al pasarle un número de error, te devuelve la cadena descriptiva correspondiente. Muchas funciones de librería y system calls en el caso de un error actualizan el valor de una variable global, errno, que contiene, en cualquier momento, el valor del último error de ejecución. Existe otra variable global, _sys_errlist, que contiene las cadenas correspondientes a cada errno, de modo que antes de que otra parte del programa en ejecución modifique el valor de errno, deberíamos ubicar en _sys_errlist la cadena de error que queremos tratar. Como adelantado, esta última operación se puede realizar usando la strerror(), de la que ya hemos hablado aquí de manera indirecta. Cierro el paréntesis)

Se dijo: decisiones. La strerror() tiene muchas personalidades, entonces, ¿cual elijo? ¿la strerror() o la strerror_r()? Y si utilizo esta última, ¿elijo la versión XSI-compliant o la versión GNU-specific? (sin mencionar, luego, las otras variantes, la strerror_l(), la strerror_s(), etc., pero estas son variantes secundarias).

Comencemos con la primera pregunta: ¿strerror() o strerror_r()? Antiguamente existía solo la primera, pero luego aparecieron los thread y comenzaron los problemas, porque en el código multi-thread hay algunas partes críticas donde solo deberían usarse las funciones thread-safe. La strerror() no está declarada thread-safe en el estándar, y para entender por qué es suficiente analizar una implementación simplificada (aunque muy similar a las implementaciones reales que podemos encontrar en las diversas libc disponibles). ¡Vamos con el código!
#include <stdio.h> // stdio.h incluye sys_errlist.h que declara las variables
                   // globales _sys_errlist (array errores) y _sys_nerr (num.errores)
static char buf[256]; // buffer global estático para la string a retornar

char *strerror(int errnum)
{
    // test si errnum es un valor valido
    if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) {
        // error desconocido: copio en buf un mensaje de error genérico
        snprintf(buf, sizeof(buf), "Unknown error %d", errnum);
    }
    else {
        // error conocido: copio en buf el mensaje correspondiente
        snprintf(buf, sizeof(buf), "%s", _sys_errlist[errnum]);
    }

    // retorno buf que ahora contiene el mensaje de error
    return buf;
}
es obvio por el código (bien comentado, como siempre, así que no tengo que explicarlo línea por línea) que la strerror() no devuelve directamente _sys_errlist [errnum] (y si fuera así sería thread-safe) sino crea un mensaje de error (para tratar eventuales errnum no válidos) usando un buffer global estático buf: entonces si dos thread de una aplicación usan (casi) al mismo tiempo, la strerror() el contenido de buf no será fiable (prevalece el thread que ha escrito por ultimo).

(Otro paréntesis: no es imposible escribir una strerror() que sea thread-safe, y en algunos sistemas sí lo es: pero dado que según el estándar no lo es, no podemos estar seguros de que en el sistema que estamos usando (o en el sistema en que un día se ejecutará la aplicación que estamos escribiendo) no haya una implementación como la que acabamos de describir, así que...)

Entonces, para el software multi-thread, ha nacido la strerror_r() que es thread-safe. ¿Cómo funciona? ¡Vamos con el código!
#include <stdio.h> // stdio.h incluye sys_errlist.h que declara las variables
                   // globales _sys_errlist (array errores) y _sys_nerr (num.errores)

char *strerror_r(int errnum, char *buf, size_t buflen);
{
    // test si errnum es un valor valido
    if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) {
        // error desconocido: copio en buf un mensaje de error genérico
        snprintf(buf, buflen, "Unknown error %d", errnum);
    }
    else {
        // error conocido: copio en buf el mensaje correspondiente
        snprintf(buf, buflen, "%s", _sys_errlist[errnum]);
    }

    // retorno buf que ahora contiene el mensaje de error
    return buf;
}
también en este caso se trata de un código simplificado, pero muy cercano a la realidad: el truco es simple, en lugar de usar un buffer global estático (que es el origen de los problemas de la strerror()) el que llama la función debe preocuparse de asignar y pasar un buffer (y su longitud) a la strerror_r(). De esta forma, el buffer que usa strerror_r() es local para el thread que lo llama, y no puede ser sobrescrito por otro thread concurrente. Hemos sacrificado un poco de simplicidad de uso ¡pero hemos conseguido el deseado comportamiento thread-safe!

Y ahora añadimos una pequeña complicación: la versión de strerror_r() que se acaba de mostrar es la GNU-specific. Pero, desafortunadamente, existe también la  XSI-compliant, que es la siguiente:
int strerror_r(int errnum, char *buf, size_t buflen);
Como se puede ver, esta segunda versión no devuelve el buffer con la error-string, y devuelve, en vez, un código de error, y la cadena encontrada hay que buscarla directamente en el buffer que hemos pasado. Con respecto al código de error, es 0 en caso de éxito, y dependiendo de la versión de libc en uso, puede devolver -1 si hay un error (seteando errno con el valor de error específico) o un valor positivo correspondiente a errno (bah, este comportamiento dual no es realmente el máximo en simplicidad de uso...). Para usar esta versión o la GNU-specific, hay que jugar correctamente con los flag _GNU_SOURCE, _POSIX_C_SOURCE y _XOPEN_SOURCE del preprocesador (como se describe en el manual de la strerror()).

Y ahora estamos listos para la segunda decisión: ¿qué usamos, la GNU-specific o la XSI-compliant? Bueno, yo diría que cuando escribimos código para tratar los códigos de error probablemente no nos interesa tratar también los errores generados en esta fase (y en la siguiente fase, etc., etc., un loop infinito de búsqueda de errores); estamos interesados, en vez, en escribir código lineal y simple... para quitarnos la duda podemos analizar dos pequeños ejemplos de uso:
GNU-specific
if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    // error socket()
    char errbuf[MAX_ERROR_LEN];    // buffer para strerror_r()
    printf("socket() error (%s)\n", strerror_r(errno, errbuf, sizeof(errbuf)));
    return EXIT_FAILURE;
}
XSI-compliant
if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    // error socket()
    char errbuf[MAX_ERROR_LEN];    // buffer para strerror_r()
    int my_error = strerror_r(errno, errbuf, sizeof(errbuf)));
    if (! my_error)
        printf("socket() error (%s)\n", errbuf);
    else {
        // proceso el error (¿quizás usando otra vez strerror_r()?)
        ...
    }

    return EXIT_FAILURE;
}
No sé lo que pensáis vosotros, ¡pero yo siempre uso la versión GNU-specific! A vosotros la eleccion...

¡Hasta el próximo post!