sábado, febrero 12, 2011

Los problemas de readdir_r

Tradicionalmente, cuando se quiere leer un directorio en Unix, se usa las API opendir - para abrir un directorio -,readdir - para obtener una estructura dirent - y closedir - para terminar con el proceso de la estructura DIR - que nos devuelve opendir. Un código sencillo que usa estas función sería tal que así:

DIR *dir;
struct dirent *p;
if ((dir = opendir ("path_directorio"))!=NULL"){
        while ((p = readdir (dir))!=NULL){
                /*
                 Haz lo que quieras con las entradas
                 del directorio d->d_name
                */
                
        }
        closedir (dir);
}

Si nos fijamos en el código anterior, readir nos está devolviendo un puntero a una estructura que reserva el sistema y que es estática, es decir, que no podemos usar esa llamada en programas multihilos sin usar las correspondientes primitivas de sincronización que nos aseguren que sólo un hilo está está llamando a readdir en un momento determinado. Para evitar estas limitaciones, está la llamada readir_r, a la cual se le va a pasar un puntero a la estructura dirent y de esta manera nos aseguramos que sea reentrante, porque no depende de ningún bloque estático reservado por el sistema. El prototipo de la función para llamarla es tal como sigue

int readdir_r(DIR *restrict dirp, struct dirent *restrict entry, struct dirent **restrict result);

Sin embargo, buscando información en Internet di con readdir_r considered harmful y empecé a leerlo, el cual me puso sobre la pista de un tema muy curioso de la declaración de struct dirent. Esta estructura tiene un campo, d_name que va a ser rellenado por la función readline_r con el nombre de la entrada de directorio que vaya recorriendo. Pero claro, cada sistema de ficheros tienen una longitud de nombre distinta, y su miramos la especificación del Opengroup, no se especifica cual es la longitud máxima que debe de tener ese campo. Nos encontramos que en algunos sistemas, como por ejemplo Linux o MacOS X está declarado como char d_name[255], pero en otros como Solaris está declarado como d_name[1]. Así el siguiente código funcionará perfectamente en Linux o MacOS X y acabará con la pila sobreescrita en Solaris.
#include <dirent.h>
DIR *dir;
struct dirent dentry;
struct dirent *p;
if ((dir = opendir ("path"))!=NULL){
        while (readdir_r(dir,&dentry,&p)==0 && p!=NULL){
                /* usa la información de dentry */
        }
        closedir (dir);
}
Aparte, el autor de readdir_r considered harmful apunta a una condición de carrera que puede ocurrir si se usa fpathconf o pathconf con el parámetro _PC_NAME_MAX para obtener el tamaño máximo de un nombre en un directorio. Apunta a una posible solución que puede consultarse en el artículo enlazado para determinar cual debe de ser el tamaño de la estructura. Quizás la manera más rápida de obtener la longitud, supuesto que el sistema soporte la llamada dirfd que nos devuelve el descriptor de fichero asociado con el directorio abierto, sea usarla pasándole el valor de DIR * devuelto por opendir y luego llamar a fpathconf, pasando este descriptor y como segundo parámetro _PC_NAME_MAX. Además, hay que tener en cuenta el terminador de cadena y siempre reservar un poco más para estar en el lado seguro. Tal que así:
#include <dirent.h>
struct dirent *alloc_dirent (DIR *dir){
struct dirent *p = NULL;
int desc;long value;
if ((desc = dirfd (dir)!=-1){
        if ((value = fpathconf (desc,_PC_NAME_MAX)) != -1){
                /* Como mínimo 255 bytes*/
                value = max (value,255);
                p = malloc (value+sizeof (struct dirent)+1);
        }
}
return p;
}

Technorati Tags: ,

No hay comentarios: