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: ,

domingo, febrero 06, 2011

Macros en C con número variable de parámetros

A veces me he encontrado con la necesidad de realizar una macro que tenga un número variable de parámetros. ¿Cómo implementa esta funcionalidad el preprocesador de C?. Pues de la siguiente manera:

  • Usamos la elipsis (...) a la hora de definir en la macro, como si estuviésemos definiendo una función
  • Cuando queremos usar los parámetros libres usamos el identificador __VA_ARGS__, que el preprocesador sustituirá por los parémtros que se le han pasado a la macro.
Un ejemplo del uso de esta construcción puede verse en el siguiente fichero de cabecera:
#ifndef _DEBUG_H
#define _DEBUG_H 1
#ifdef DEBUG
        #define debug(format,...) output_debug (__FUNCTION__,__LINE__,format,__VA_ARGS__);
#else
        #define debug(format,params)
#endif
int output_debug (const char *funcname,unsigned int line,const char *format,...);
#endif

Existe un punto donde la anterior sintaxis puede dar problemas, como es el caso de que no se pase ningún parámetro a la macro: El estándar C obliga a que al menos un parámetro sea pasado a la elipsis para evitar dejar una coma colgada. El gcc tiene varias extensiones para tratar con este problema, mientras que el Visual C++, si no se pasan ningún argumento suprime la coma directamente. En el caso del gcc usa la siguiente construcción, que sólo es válida delante de una coma:

#define  debug  (format,...) output_debug (__FUNCTION__,__LINE__,format,##__VA_ARGS__);
En este caso los símbolos ## hace que se elimine la coma si no se pasan parámetros.

Referencias

  1. Variadic Macrod
  2. Variadic Macros (Visual Studio)

Technorati Tags:

viernes, febrero 04, 2011

¿Cómo se si un descriptor de fichero está conectado a una tty?

Aunque casi siempre que he programado en C lo he hecho bajo Unix, nunca he tocado esa parte que corresponde al uso de las tty. El sistema tiene una llamada que te dice si un sistema la tiene, que es isatty, pero antes de existir esta llamada había una manera más pedreste de conseguirlo, que la vi en una referencia del Advanced Programing in the Unix enviroment, y es intentar obtener las propiedades del descriptor a través de tcgetattr. Teniendo en cuenta que Stevens es un gran escritor, creo que voy a hacerme con el libro, porque el estilo de Stevens es altamente didáctivo. Lástima que nos dejara hace diez años.

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

int main (int argc,char **argv){
        struct termios ts;
        if (tcgetattr (fileno (stdin),&ts)!=-1){                 printf ("stdin is  a tty\n");
        }else{
                printf ("stdin is not a tty\n");
        }
}

Technorati Tags: