===== 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; size_t nbytes; ssize_t 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 #include #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 #include 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 #include 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 #include #include 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 #include #include #include #include #include #include #define SIZE 8192 int main(int argc, char *argv[]) { int in, out; ssize_t n; char buf[SIZE]; if (argc!=3) { fprintf(stderr, "Use: %s \n", argv[0]); exit(1); } if ((in= open(argv[1], O_RDONLY))==-1) { perror(argv[1]); exit(2); } if ((out= creat(argv[2], 0666))==-1) { perror(argv[2]); exit(3); } /* usando stats se pueden mantener los permisos */ while ((n= read(in, buf, SIZE))>0) { if (write(out, buf, n)!=n) { perror(argv[2]); exit(4); } } if (n<0) { perror(argv[1]); exit(5); } close(in); close(out); return 0; } ==== Cambiar los permisos de un archivo ==== #include char *path; mod_t mode; chmod(path, mode); ==== Borrar un archivo ==== Se usa la funcion //unlink//: #include char *path; unlink(path); ==== Cambiar el nombre de un archivo ==== Se usa la funcion //rename//: #include char *oldpath, *newpath; rename(newpath, oldpath); ==== Crear un link duro ==== Se usa la función //link//: #include 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. También se puede crear un link duro en un directorio distinto del directorio en donde aparece el nombre original del archivo. La única restricción es que //ambos nombres se deben encontrar en directorios de la misma partición de disco//. ==== Crear un link simbólico ==== Se usa la función //symlink// para crear un nombre sinónimo de un archivo existente: #include char *path, *newpath; symlink(path, newpath); 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! La desventaja de los links simbólicos (con respecto a los links duros) es que si se borra datos.txt, datos2.txt ya no es válido. La ventaja con respecto a los links duros es que sí se pueden crear links simbólicos con el nuevo nombre en una partición distinta de la partición en donde se ubica el nombre original. ==== Acceso directo ==== Se usa la función //lseek//: #include #include 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 #include /* 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 en caso de error */ chdir(name); /* cambia el directorio actual. Retorna !=0 en caso de error. */ Llamadas al sistema (sección 2): #include 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 de creación 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? Esta función es usada por el comando ls -l para averiguar las características de un archivo: $ ls -l index.html -rw-r--r-- 1 lmateu inv 3851 Aug 16 2007 index.html La mayoría de estos atributos se pueden cambiar con llamadas al sistema, como por ejemplo: chmod(path, perms); chown(path, owner, group); utime(path, ...); La función utime (ver manual) permite "falsificar" el "time stamp" de un archivo. 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 === Dada la existencia de los links duros, el nombre de un archivo no basta para identificarlo, porque 2 nombres distintos pueden referirse al mismo archivo. La verdadera identidad de un archivo consiste en la identificación de la partición de disco en donde se ubica (campo st_dev) y el inodo asignado (campo st_ino). Usando estos 2 campos se puede programar un comando que determina si dos nombres están asociados al mismo archivo. Llamaremos a este comando linked: 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: /* linked.c */ #include #include #include #include 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 #include #include #include #include #include #include #include #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.