¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Manejo de Archivos en Unix
Unix provee una interfaz uniforme, en que todos los dispositivos se ven como archivos. Por ejemplo:
/home/lmateu/datos.txt /bin/ls /dev/tty1
El primero corresponde a un archivo normal en donde se almacena algún tipo de datos. El segundo es el archivo binario ejecutable del comando ls. El tercero no es realmente un archivo en disco, si no que representa un terminal conectado a través de una puerta serial. Escribir un string en /dev/tty1 significa hacer que se escriba ese string en la pantalla de ese terminal. Leer de /dev/tty1 significa esperar a que el usuario de ese terminal ingrese algo en el teclado y presione la tecla enter. De la misma forma existen pseudo archivos en el directorio /dev que representan los discos, el CD, el mouse, etc.
Al abrir un archivo en Unix, se recibe un número entero pequeño mayor o igual que cero, que se llama un file descriptor (fd). Nótese que esto es distinto de lo que ocurre al abrir un archivo en la biblioteca standard de C, en que se recibe un file pointer.
Este fd identifica al archivo abierto para todas la operaciones (leer, escribir, etc.).
Todo programa comienza con tres fds ya abiertos:
fd | función |
---|---|
0 | entrada estándar |
1 | salida estándar |
2 | salida estándar de errores |
Lectura/escritura de archivos
Las operaciones básicas de entrada/salida son:
char *buf; int fd; ssize_t nbytes, leidos, escritos; leidos = read(fd, buf, nbytes); escritos = write(fd, buf, nbytes);
Cada llamada retorna el número de bytes transferidos. Aunque la operación read usualmente entrega la cantidad de bytes pedidos, por ejemplo cuando se lee desde un archivo, también puede entregar menos de lo pedido, por ejemplo cuando se lee del terminal. En el caso del write es un error que éste no sea igual al número pedido. En caso de fin de archivo se retorna un cero y, en caso de error, un -1.
Ejemplo: copiar
/* Copia stdin -> stdout */ #include <stdio.h> #include <unistd.h> #define BUFSIZE 8192 int main() { char buf[BUFSIZE]; ssize_t n; while((n= read(0, buf, BUFSIZE))>0) write(1, buf, n); return 0; }
La diferencia de esta versión con una que se hizo previamente y que recurría a fread y fwrite es que ahora se usan las funciones read y write de la API de Unix. Estos significa que no todas las implementaciones de C proveen read y write, pero todas deben ofrecer fread y fwrite.
Esta nueva versión puede ser más o menos rápida que la de fread y fwrite según el tamaño del buffer. Si BUFSIZE es 1, será terriblemente más lenta por el número de llamadas a Unix. Las llamadas a Unix son caras porque llevan asociadas una interrupción. Por otra parte si BUFSIZE es 8192, será igualmente rápida. De hecho en un sistema Unix fread y fwrite típicamente se implementan en base a read y write usando un buffer de 8192 bytes. Sin embargo para mayor eficiencia se puede usar un buffer de un megabyte logrando así la mayor tasa de transferencia entre el disco y la memoria del computador.
Ejemplo: migetchar
Las funciones read y write también se pueden usar para construir funciones de más alto nivel, como por ejemplo nuestro propio getchar:
#include <stdio.h> #include <unistd.h> int migetchar() { char c; return (read(0, &c, 1)==1? c : EOF); } int main() { int c; while( (c= migetchar()) != EOF) putchar(c); return 0; }
Como se dijo previamente, leer de a un bytes es ineficiente. La siguiente versión lee de a 8192 bytes:
#include <stdio.h> #include <unistd.h> int migetchar() { static char buf[BUFSIZ]; /* Definido en stdio.h. Típicamente 8192 */ static char *p=buf; static ssize_t n=0; if(n==0) { /* buffer vacio */ n=read(0,buf, sizeof buf); p=buf; } return (--n>=0? *p++ : EOF); } int main() { int c; while( (c= migetchar()) != EOF) putchar(c); return 0; }
Abrir y cerrar archivos
Para abrir un archivo se usa open:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> fd= open(path, flags, perms); /* flags puede ser O_RDONLY, O_WRONLY, O_RDWR */ /* Además se le puede superponer los flags siguientes: O_APPEND ir al final antes de cada write O_CREAT crea el archivo si no existe O_EXCL junto con O_CREAT, falla si el archivo ya existe O_TRUNC trunca el archivo a largo cero El parámetro perms se usa solo junto con O_CREAT */
para cerrar un archivo se usa close:
close(fd);
La llamada:
fd= creat(path, perms);
crea el archivo con los permisos indicados y equivale a:
fd= open(path, O_WRONLY|O_CREAT|O_TRUNC, perms);
Permisos
Se identifican 3 categorías de usuarios: el propietario (owner), el grupo (group) y otro (other). Para cada categoría existen 3 bits que indican si los usuarios en esa categoría pueden leer, escribir o ejecutar.
owner group other r w x r w x r w x
Típicamente los permisos se expresan en octal porque un dígito octal corresponde exactamente a los 3 bits para un tipo de usuario. Ejemplos:
0755 rwx para owner, rx para los demás 0666 rw para todos
Ejemplo: copia de archivos
/* Uso: micp from to */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <fcntl.h> #include <unistd.h> void error(char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "Error: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); exit(1); } int main(int argc, char *argv[]) { int f1, f2; ssize_t n; char buf[BUFSIZ]; if (argc!=3) error("Use: my_cp from to"); if ((f1= open(argv[1], O_RDONLY))==-1) error("Can't open %s", argv[1]); if ((f2= creat(argv[2], 0666))==-1) error("Can't open %s", argv[2]); /* usando stats se pueden mantener los permisos */ while ((n= read(f1, buf, BUFSIZ))>0) if(write(f2, buf, n)!=n) error("Write error on file %s", argv[2]); return 0; }
Cambiar los permisos de un archivo
#include <sys/stat.h> char *path; mod_t mode; chmod(path, mode);
Borrar un archivo
Se usa la funcion unlink:
#include <unistd.h> char *path; unlink(path);
Crear un link duro
Se usa la función link:
#include <unistd.h> char *path, *newpath; link(path, newpath);
Se usa para crear un nombre sinónimo de un archivo existente. También se puede crear un link desde el shell de comandos:
% ln datos.txt datos2.txt
En este caso datos.txt y datos2.txt se refieren al mismo archivo. Si se edita datos.txt y se introduce un cambio, datos2.txt va a reflejar ese cambio. Si se borra datos.txt, el archivo datos2.txt sigue siendo válido.
Crear un link simbólico
Se usa la función symlink:
#include <unistd.h> char *path, *newpath; symlink(path, newpath);
Se usa para crear un nombre sinónimo de un archivo existente. También se puede crear un link simbólico desde el shell de comandos:
% ln -s datos.txt datos2.txt
En este caso datos2.txt es un nombre alternativo para datos.txt. ¡Pero cuidado! Si se borra datos.txt, datos2.txt ya no es válido.
Acceso directo
Se usa la función lseek:
#include <sys/types.h> #include <unistd.h> int fd; off_t offset; int how; /* normalmente SEEK_SET (posición absoluta), pero también * puede ser SEEK_CUR (relativo) o SEEK_END (fin archivo). */ lseek(fd, offset, how);
Ejemplos:
lseek(fd, 0L, SEEK_END); /* para append */ lseek(fd, 0L, SEEK_SET); /* rewind */
La función lseek retorna la nueva posición dentro del archivo, o -1 si hubo un error.
Manejo de Directorios
Los directorios son archivos especiales que contienen listas de archivos en su interior. Para su manejo existen funciones especiales de la biblioteca estándar de C (sección 3):
#include <sys/types.h> #include <dirent.h> /* Esto define un "struct dirent" que incluye un * char d_name[NAME_MAX]; */ DIR *dirp; dirp= opendir(dirname); /* NULL en caso de error */ struct dirent *d; d= readdir(dirp); /* entrega siguiente nombre de archivo en d->d_name, retorna NULL al final */ closedir(dirp); rewinddir(dirp); /* vuelve al inicio */ buf= getcwd(buf, size); /* guarda en buf pathname absoluto del directorio actual, retorna NULL si error */
Llamadas al sistema (sección 2):
#include <unistd.h> status= chdir(path); /* 0 => OK, -1 => error */ mkdir(path, perms); rmdir(path); link(oldpath, newpath); /* no funciona para directorios */ unlink(path); rename(oldpath, newpath);
Características de un archivo
Para averiguar atributos de un archivo, se usa la llamada a sistema stat:
stat(path, buf); /* buf es de tipo "struct stat *" */
La estructura stat tiene campos tales como:
st_mode modo (=perms) st_ino inode st_dev device st_nlink número de links st_uid user id del owner st_gid grupo st_size tamaño del archivo en bytes st_atime dia y hora del último acceso st_ctime día y hora del último cambio (p.ej. perms) st_mtime día y hora de la última modificación
Hay macros para “testear” el modo:
S_ISDIR directorio? S_ISCHR char special device? S_ISBLK block special device? S_ISREG regular file? S_ISFIFO named pipe? S_ISLNK link simbólico?
Los atributos se pueden modificar con las llamadas a sistema:
chmod(path, perms); chown(path, owner, group);
También es posible falsificar el “time stamp” de un archivo, con la función utime (ver manual). Esto lo usa el comando tar para restaurar la fecha y hora de creación y modificación de los archivos.
Ejemplo 1: Determinar si 2 nombres de archivos corresponden al mismo archivo
Ejemplo de uso:
% cp a.txt b.txt % ln a.txt c.txt % linked a.txt b.txt not linked % linked a.txt c.txt linked %
Esta es la implementación usando stat:
#include <stdio.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char **argv) { struct stat st1, st2; if (stat(argv[1], &st1)!=0) { fprintf(stderr, "can't stat %s\n", argv[1]); exit(1); } if (stat(argv[2], &st2)!=0) { fprintf(stderr, "can't stat %s\n", argv[2]); exit(1); } if (st1.st_dev==st2.st_dev && st1.st_ino==st2.st_ino) printf("linked\n"); else printf("not linked\n"); return 0; }
Si 2 nombres de archivos están en directorios de particiones distintas, entonces no pueden ser el mismo archivo. La identificación de una partición se almacena en st_dev. Si están en la misma partición y corresponden al mismo archivo entonces tendrán el mismo valor en st_ino.
Ejemplo 2: Listar directorios recursivamente
#include <stdio.h> #include <stdarg.h> #include <sys/types.h> #include <dirent.h> #include <sys/stat.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MAX_PATH 1000 void listdir(int indent, char *name) { DIR *current_dir; struct dirent *this_entry; char cwd[MAX_PATH+1]; int i; struct stat status; /* imprimimos directorio actual */ for (i= 1; i<=indent; i++) printf(" "); printf("%s\n", name); if (stat(name, &status)!=0) error("Can't stat %s", name); if(!S_ISDIR(status.st_mode)) return; /* abrimos el directorio para lectura */ if ((current_dir= opendir(name))==NULL) error("Can't open directory %s", name); if (getcwd(cwd, MAX_PATH+1)==NULL) error("Can't get cwd"); if (chdir(name)!=0) error("Can't chdir"); while ((this_entry= readdir(current_dir))!=NULL) { if (strcmp(this_entry->d_name, ".")==0 || strcmp(this_entry->d_name, "..")==0) continue; listdir(indent+1, this_entry->d_name); } closedir(current_dir); chdir(cwd); } int main() { listdir(0, "."); return 0; }
Ejercicio
- Modifique el programa de más arriba de modo que entregue el tamaño del archivo. Use stat para ello.
- Modifique el programa mi_cp para que no haga la copia cuando el archivo de destino ya existía.