Subsecciones

18. Acceso de Archivos y llamadas al sistema de directorios

Existen muchas utilerías que permiten manipular directorios y archivos. Los comandos cd, ls, rm, cp, mkdir, etc. son ejemplos que han sido revisados ya en el sistema operativo.

Se revisará en este capítulo como hacer las mismas tareas dentro de un programa en C.

18.1 Funciones para el manejo de directorios <unistd.h>

Para realizarlo se requiere llamar las funciones apropiadas para recorrer la jerarquía de directorios o preguntar sobre los contenidos de los directorios.

Se tiene a continuación la emulación del comando cd de Unix con C:

#include <stdio.h>
#include <unistd.h>

#define TAM 80

main(int argc, char **argv)
{
	char cadena[TAM];

	if (argc < 2) 
	{ 
		printf("Uso: %s <directorio> \n",argv[0]);
		exit(1);
	}

	if (chdir(argv[1]) != 0)
	{ 
		printf("Error en %s.\n",argv[0]);
		exit(1);
	}

	getcwd(cadena,TAM);

	printf("El directorio actual es %s\n",cadena);


}

18.1.1 Busqueda y ordenamiento de directorios: sys/types.h,sys/dir.h

Dos funciones útiles (en plataformas BSD y aplicaciones no multi-thread) están disponibles:

int scandir(const char *dir, struct dirent **listanomb,
int (*select)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **)

Esta función rastrea el directorio dir, llamando select() en cada entrada de directorio. Las entradas para las que select() devuelve un valor distinto de cero se almacenan en cadenas que se asignan de la memoria con malloc(), ordenadas usando qsort() con la función de comparación compar(), y puestas en la matriz listanomb. Si select es NULL, se seleccionan todas las entradas.

int alphasort(const struct dirent **a, const struct dirent **b);

Puede ser usada como función de comparación para que la función scandir() ponga las entradas de directorio en orden alfabético.

A continuación se tiene una versión simple del comando de UNIX ls

#include <dirent.h>
#include <unistd.h>
#include <sys/param.h> 
#include <stdio.h>
 
#define FALSO 0 
#define VERDADERO !FALSO 
 
extern int alphasort();
 
char trayectoria[MAXPATHLEN];
 
main()
{ 
	int contar,i;
	struct dirent **archivos;
	int selecc_arch();
 
	if ( getwd(trayectoria) == NULL )
	{ 
		printf("Error obteniendo la trayectoria actual\n");
		exit(0);
	}
	printf("Directorio de trabajo actual: %s\n",trayectoria);
	contar = scandir(trayectoria, &archivos, selecc_arch, alphasort);
 
	/* Si no se encontraron archivos */
	if (contar <= 0)
	{ 
		printf("No hay archivos en este direntorio\n");
		exit(0);
	}
	printf("Hay %d archivos.\n",contar);
	for (i=0; i<contar; ++i)
		printf("%s  ",archivos[i]->d_name);
	printf("\n"); /* flush buffer */
}

int selecc_arch(struct dirent *entry)
{
	if ((strcmp(entry->d_name, ".") == 0) ||
		(strcmp(entry->d_name, "..") == 0))
		return (FALSO);
	else
		return (VERDADERO);
}

scandir regresa los pseudodirectorios (.), (..) y también todos los archivos. En este ejemplo se filtran los pseudodirectorios para que no se muestren haciendo que la función regrese FALSO.

Los prototipos de las funciones scandir y alphasort tienen definiciones en la cabecera dirent.h

MAXPATHLEN esta definida en sys/param.h y la función getwd() en unistd.h.

Se puede ir más lejos que lo anterior y buscar por archivos particulares.

Reescribiendo la función selecc_arch para que sólo muestre los archivos con los sufijos .c, .h y .o.

int selecc_arch(struct dirent *entry)
{
	char *ptr;
                
	if ((strcmp(entry->d_name, ".")== 0) ||
		(strcmp(entry->d_name, "..") == 0))
		return (FALSO);

	/* Revisar las extensiones de los archivos */
	ptr = rindex(entry->d_name, '.'); /* Probar que tenga un punto */

	if ( (ptr != NULL ) &&
		   (  (strcmp(ptr, ".c") == 0)
		   || (strcmp(ptr, ".h") == 0)
		   || (strcmp(ptr, ".o") == 0) )
		)
		return (VERDADERO);
	else
		return(FALSO);
}

La función rindex() es una función para manejo de cadenas que regresa un apuntador a la última ocurrencia del caracter c en la cadena s, o un apuntador NULL si c no ocurre en la cadena. (index() es una función similar pero asigna un apuntador a la primera ocurrencia.)

18.2 Rutinas de manipulación de archivos: unistd.h, sys/types.h, sys/stat.h

Existen varias llamadas al sistema que pueden ser aplicadas directamente a los archivos guardados en un directorio.

18.2.1 Permisos de accesos a archivos

La función int access(const char *trayectoria, int modo); -- determina los permisos de usuario para un fichero, de acuero con modo, que esta definido en #include <unistd.h>, los cuales pueden ser:

La función access() regresa: 0 si ha habido éxito, o -1 en caso de falla y a errno se le asigna un valor adecuado. Ver las páginas del man para ver la lista de errores.

18.2.1.1 errno

Algunas llamadas al sistema (y algunas funciones de biblioteca) dan un valor al entero errno para indicar que ha habido un error. Esta es una variable especial del sistema.

Para usar errno en un programa de C, debe ser declarado de la siguiente forma:

extern int errno;

Esta puede ser manualmente puesto dentro de un programa en C, de otra forma simplemente retiene el último valor.

La función int chmod(const char *trayectoria, mode_t modo); cambia el modo del archivo dado mediante trayectoria para un modo dado.

chmod() devuelve 0 en caso de éxito y -1 en caso de error además se asigna a la variable errno un valor adecuado (revisar las páginas de man para ver los tipos de errores)

18.2.2 Estado de un archivo

Se tienen dos funciones útiles para conocer el estado actual de un archivo. Por ejemplo se puede sabe que tan grande es un archivo (st_size), cuando fue creado (st_ctime), etc (ver la definición de la estructura abajo). Las dos funciones tienen sus prototipos en <sys/stat.h>

Las funciones stat() y fstat() regresan 0 en caso éxito, -1 si hubo error y errno es actualizado apropiadamente.

buf es un apuntador a la estructura stat en la cual la información es colocada. La estructura stat esta definida en include <sys/stat.h>, como sigue:

struct stat
{
    dev_t         st_dev;      /* dispositivo */
    ino_t         st_ino;      /* inodo */
    mode_t        st_mode;     /* proteccion */
    nlink_t       st_nlink;    /* numero de enlaces fisicos */
    uid_t         st_uid;      /* ID del usuario propietario */
    gid_t         st_gid;      /* ID del grupo propietario */
    dev_t         st_rdev;     /* tipo dispositivo (si es
                                  dispositivo inodo) */
    off_t         st_size;     /* tamaño total, en bytes */
    unsigned long st_blksize;  /* tamaño de bloque para el
                                  sistema de ficheros de E/S */
    unsigned long st_blocks;   /* numero de bloques asignados */
    time_t        st_atime;    /* hora ultimo acceso */
    time_t        st_mtime;    /* hora ultima modificacion */
    time_t        st_ctime;    /* hora ultimo cambio */
};

Se muestra un ejemplo que hace uso de la función stat:

#include <stdio.h>
#include <sys/stat.h> /* Para la estructura stat */
#include <unistd.h>

main(int argc, char **argv)
{
	struct stat buf;

	printf("%s\n",argv[0]);
	
	if ( stat(argv[0], &buf) == -1 )
	{
		perror(argv[0]);
		exit(-1);
	}
	else
	{
		printf("Tamaño del archivo %s %d bytes.\n",argv[0],buf.st_size);
	}
}

18.2.3 Manipulación de archivos: stdio.h, unistd.h

Existen algunas funciones para borrar y renombrar archivos. Quizás la forma más común es usando las funciones de stdio.h:

int remove(const char *pathname);
int rename(const char *viejo, const char *nuevo);

Dos llamadas al sistema (definidas en unistd.h) las cuales son actualmente usadas por las funciones remove() y rename() también existen, pero son probablemente más díficiles de recordar, al menos que se este suficientemente familiarizado con UNIX.

Las funciones stat() y fstat() regresan 0 en caso éxito, -1 si hubo error y pone la variable errno con algún valor indicando el tipo de error.

18.2.4 Creación de archivos temporales: <stdio.h>

Los programas con frecuencia necesitan crear archivos sólo durante la vida de un programa. Existen dos funciones convenientes (además de algunas variantes) para la realización de la tarea mencionada. El manejo (borrado de archivos, etc.) es tomado con cuidado por el sistema operativo.

La función FILE *tmpfile (void); crea un archivo temporal (en modo de lectura/escritura binaria) y abre el correspondiente flujo. El archivo se borrará automáticamente cuando el programa termine.

La función char *tmpnam(char *s); crea un nombre único para un archivo temporal usando el prefijo de trayectora P_tmpdir definido en <stdio.h>.

18.3 Ejercicios

  1. Escribir un programa en C para simular el comando ls -l de UNIX que muestre todos los archivos del directorio actual, sus permisos, tamaño, etc.
  2. Escribir un programa para mostrar las líneas de un archivo que contenga una palabra dada como argumento al programa, es decir, una versión sencilla de la utilidad grep de UNIX.

  3. Esribir un programa para mostrar una lista de archivos dados como argumentos, parando cada 20 líneas hasta que una tecla sea presionada (una versión simple de la utilería more de UNIX).

  4. Escribir un programa que liste todos los archivos del directorio actual y todos los archivos en los subsecuentes directorios.