Programación de Sistemas. Tema IV Archivos. + Entrada y Salida en UNIX. UNIX fue diseñado para soportar la entrada y salida de una manera uniforme. Para esto, UNIX tiene como abstracción del sistema operativo el concepto de un archivo. Para UNIX, un archivo es un conjunto de caracteres que se almacena en un medio persistente (disco, cdrom, cinta). Cualquier dispositivo periférico es manejado por un manejador de dispositivo (device driver). El usuario de UNIX puede manipular dichos manejadores utilizando archivos especiales sobre los cuales se pueden aplicar operaciones típicas de un archivo. Por tanto, al manejar la E/S en UNIX, se pueden aplicar operaciones sobre archivos, ya sean los archivos especiales del manejador del dispositivo: o archivos que contienen información. + Organización del sistema de archivos. UNIX decidió implantar su sistema de archivos utilizando un módelo de árbol. Cada nodo del arbol es un archivo o es un contenedor de archivos. El contenedor de archivos es conocido como un directorio, pero UNIX considera a los directorios tambien como archivos, y un directorio es un archivo que contiene la referencia a archivos y otros directorios (subdirectorios). Dado que los archivos están organizados en una estructura de árbol, para poder tener accesos a cualquier nodo del árbol, es necesario proporcionar la ruta o el camino a seguir dentro del árbol. Esto implica, que cada nodo o archivo del árbol tiene un nombre específico, y para poder manejar cada nodo, debe proporcionarse una ruta. El nódo inicial de la jerarquía no tiene nombre alguno y es conocido como el directorio raíz (root). Para manipularlo, se hace referencia a él dando la ruta, que es /. Este nodo raíz contiene mas subdirectorios, por ejemplo /usr, /etc/, /home, entre otros. Y cada subdirectorio contiene archivos o mas subdirectorios. El caracter separador de una ruta es /. Este caracter se incorpora como parte de la ruta. Para accesar un archivo siempre se debe dar toda la ruta, conocida como la ruta absoluta. El shell del usuario permite abreviar esta ruta, utilizando el concepto de ruta relativa, y que es con respecto al directorio o nodo donde el usuario o proceso se encuentre ubicado. Ejemplo de ruta absoluta es /home/alumno/cib61210/tema4.taz Ejemplo de ruta relativa es ./tema4.taz El directorio actual queda representado como el caracter de . El directorio a un nivel superior del actual es representado como .. Ejercicios. 1. Navegar en el sistema de archivos y : +) leer el archivo hosts, localizado bajo el directorio /etc. +) Cambiarse al directorio /var/spool +) Cambiarse al directorio /home +) Regresar al directorio de inicio de sesion ($HOME) + Representación del sistema de archivos en UNIX. UNIX no concibe los discos como volúmenes o unidades de disco. Para poder accesar a un disco, este se debe particionar. Al crear una partición, se utilizan las herramientas para dar formato a un disco. Sobre cada partición se puede construir un sistema de archivos. Esta creación se realiza una vez, normalmente al instalar al disco. La construcción del sistema de archivos consiste en crear un conjunto de estructuras de datos y grabarlas en el disco, que posteriormente las utiliza el kernel ante cualquier manipulación de los archivos. Cada partición, al tener un sistema de archivos, es "montada" o anexada al árbol del sistema de archivos. Gracias a esto, UNIX puede crecer sin limite (teóricamente) en el número de sistemas de archivos que puede manejar una computadora, ya que puede incorporar sistemas de archivos de varios discos o aún mas, de diferentes sistemas de cómputo. La estructura de datos principal es conocida como un inodo. Un inodo contiene información asociada con el archivo, tal como, el tamaño del archivo, el identificador del usuario y grupo a quien pertenece este archivo, algunas fechas relacionadas con el archivo, permisos y una relación o cuenta de quien tiene referencias a este archivo. Además el inodo tiene una lista de apuntadores a las direcciones del bloque de disco en donde está localizado el archivo, de tal forma que esta relación de apuntadores permite que el archivo crezca a tamaños de 2GB o más dependiendo del sistema operativo. Cuando se crea un sistema de archivos, se crean los inodos que se pueden utilizar (con respecto al tamaño del disco). Cuando UNIX accesa a un archivo, entonces lo que hace, es leer un inodo del disco y cargarlo a la memoria del kernel, para leer la información del disco. Para optimizar el acceso a los archivos, UNIX tiene en un cache los inodos que recientemente se han utilizado. Después de un tiempo, si algún inodo sufre cambios, UNIX guarda ese cambio. Si el sistema se interrumpe de forma no esperada, pueden generarse inconsistencias. Al borrar un archivo, UNIX marca como libre al inodo, y limpia la información del mismo, por lo que esto hace imposible el recuperar a un inodo. Cada sistema de archivos tiene un inodo inicial, que tiene una referencia a los archivos localizados en el primer nivel de este sistema. Se guardan varios respaldos de este inodo para evitar inconsistencias. Los procesos al manejar un archivo, no proporcionan el inodo al que quieren tener acceso, sino que indican la ruta. El kernel como respuesta, proporciona un número único, para cada proceso, denominado descriptor de archivo. El kernel carga, por cada proceso, una tabla de descriptores de archivos en donde los relaciona con el inodo del archivo utilizado. Por omisión todo proceso tiene tres descriptores de archivos asociados, entrada estándar, salida estándar y error estándar, con número 0, 1 y 2 respectivamente. Ejercicios. 1. Ejecutar el comando mount, y checar las particiones del disco de la estación de trabajo. 2. Investigar que realiza el comando newfs o mkfs 3. ¿ Por qué un sistema UNIX no puede ser apagado repentinamente ? 4. Todo archivo tiene un inodo asociado. Decir si esto es cierto o falso. 5. Que información proporciona el comando ls -i ? + Tipos de archivos. UNIX considera los siguientes tipos de archivos. + Archivo ordinario. + Directorio. + Ligas. Referencias a otros archivos. + Archivos especiales de cararacteres. (teclado, terminal, cinta, que deben leerese caracter por caracter) + Archivos especiales por bloque. Discos, CDROMS (dispostivos que pueden retrocederse o avanzar por bloques) Al aplicar el comando ls -l, se puede identificar el tipo de archivo, leyendo el primer caracter de la primera columna. Si es -, implica que es un archivo ordinario. Si es d, implica que es un directorio. Si es l, implica que es una liga. (excepto para ligas duras) Si es c, implica que es un archivo especial de caracteres. Si es b, implica que es un archivo por bloque. Ejercicios. 1. Buscar en el sistema de archivos un tipo de archivo especifico. + Operaciones sobre directorios,archivos y archivos especiales. Sobre estos tipos de archivos se pueden realizar las siguientes operaciones: Directorios + Creación de un directorio. + Destrucción de un directorio. + Obtener información sobre un directorio. + Cambiarse a un directorio específico. Archivos ordinarios. + Creación. + Apertura. + Borrado. + Obtener información. + Lectura y escritura. + Posicionamiento. + Cerrado. Archivos especiales. + Apertura. + Obtener información sobre un dispositivo. + Configuración de un dispositivo. Como en UNIX todos los archivos son tratados de forma idéntica, muchas de las llamadas que se efectuan sobre algun tipo de archivo, se puede aplicar sobre los tipos restantes. + Apertura de un archivo. open. Para poder abrir un archivo, es necesario invocar a llamada open(2) de UNIX. Esta llamada requiere de la ruta del archivo, la manera como se abre el archivo, solo escritura, creación, lectura/escritura o truncar. Opcionalmente, open puede recibir un tercer parámetro que indica los permisos con los que se desea accesar el archivo. El formato de open es: #include #include #include int open(const char *path, int oflag, /*mode_t mode*/) El valor oflag puede tomar los siguientes valores, definidos en O_RDONLY -- Abrir de solo lectura. O_WRONLY -- Abrir de solo escritura. O_RDWR -- Abrir de lectura/escritura. O_CREAT -- Crear el archivo si no existe O_APPEND -- Abrir el archivo y posicionarse al final del archivo O_TRUNC -- Abrir el archivo, y si existe, truncarlo. el valor entero que retorna es el descriptor de archivo, y que tiene un valor positivo. Si el valor es negativo, implica un error. El siguiente programa abre un archivo para solo leerlo. a partir de una ruta proporcionada por el usuario. /*Ejemplo1 -- Abrir un archivo especificado por el usuario */ #include #include #include main(int argc, char * argv[]) { char * nombrearchivo; int fd; if (argc==1) { printf("Ejemplo1: proporcionar un nombre de archivo\n"); exit(1); } else { nombrearchivo=argv[1]; } fd= open(nombrearchivo,O_RDONLY); if (fd < 0) { perror("Checar la ruta o permisos del archivo"); exit(2); } else { printf("El descriptor del archivo es %d\n", fd); exit(0); } } Al ejecutar este programa Ejemplo1 /etc/hosts El descriptor del archivo es 3 Ejemplo1 ffdfd Checar la ruta o permisos del archivo: No such file or directory Se puede abrir un archivo con modo de escritura. /*Ejemplo2 -- Abrir un archivo especificado por el usuario, modo escritura */ #include #include #include main(int argc, char * argv[]) { char * nombrearchivo; int fd; if (argc==1) { printf("Ejemplo1: proporcionar un nombre de archivo\n"); exit(1); } else { nombrearchivo=argv[1]; } fd= open(nombrearchivo,O_WRONLY); if (fd < 0) { perror("Checar la ruta o permisos del archivo"); exit(2); } else { printf("El descriptor del archivo es %d\n", fd); exit(0); } } Al ejecutar este programa wotan% Ejemplo2 /etc/passwd Checar la ruta o permisos del archivo: Permission denied wotan% Ejemplo2 /etc/hosts Checar la ruta o permisos del archivo: Permission denied wotan% Ejemplo2 Ejemplo2.c El descriptor del archivo es 3 Observar que los dos primeros archivos no se pueden abrir con modo de escritura, ya que el usuario no es propietario de estos archivos. + Creacion de un archivo. creat. Para poder crear un archivo se debe utilizar la llamada al sistema creat(2). #include #include #include int creat(const char* path, mode_t mode) se debe proporcionar la ruta del archivo a crear y los permisos de creacion (lectura, escritura o ejecucion). Si la creacion es correcta, retorna el descriptor del archivo, de lo contario un número negativo. Si el archivo existía lo destruye si y solo si se tienen permisos. El siguiente ejemplo muestra la creacion de un archivo. /*Ejemplo3 Crear un archivo*/ #include #include main(int argc, char * argv[]) { char * nombrearchivo; int fd; if (argc==1) { printf("Ejemplo1: proporcionar un nombre de archivo\n"); exit(1); } else { nombrearchivo=argv[1]; } /*if*/ /*crear con permisos de lectura y escritura para todos*/ fd = creat(nombrearchivo, 0666): if (fd < 0) { perror("Checar la ruta ,permisos del directorio o el archivo si existe"); exit(2); } else { printf("El descriptor del archivo es %d\n", fd); exit(0); } } Ejemplo3 gus El descriptor del archivo es 3 Ejemplo4 /etc/hosts #---OJO, no correr este comando como root. Checar la ruta ,permisos del directorio o el archivo si existe: Permission denied + Cerrar un archivo. close. Un archivo debe ser cerrado, con el fin de que el descriptor de archivo sea devuelto a la tabla de descriptores de archivos de cada proceso. Para cerrar un archivo, se debe aplicar la llamada close. con el siguiente formato. #include int close(int fildes) Esta llamada recibe el descriptor del archivo a cerrar y retorna un valor, que si es menor a cero, indica que el archivo no se puede cerrar. /*Ejemplo4 Abrir varios archivos, sin cerrar*/ #include main(int argc, char * argv[]) { int fd; fd= open("/etc/hosts", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); fd= open("/etc/passwd", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); fd= open("/etc/services", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); } Ejemplo4 El descriptor del archivo es 3 El descriptor del archivo es 4 El descriptor del archivo es 5 Observar que el descriptor de archivo se incrementa en un valor. Si se cierra el archivo /*Ejemplo5 Abrir varios archivos, sin cerrar*/ #include main(int argc, char * argv[]) { int fd; fd= open("/etc/hosts", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); close(fd); fd= open("/etc/passwd", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); close(fd); fd= open("/etc/services", O_RDONLY); printf("El descriptor del archivo es %d\n", fd); close(fd); } Ejemplo5 El descriptor del archivo es 3 El descriptor del archivo es 3 El descriptor del archivo es 3 + Lectura y escritura de un archivo. Sobre un archivo se puede aplicar operación de lectura y/o escritura. Para realizar esto, se utiliza la llamada read(2) y write(2) con el siguiente formato: #include ssize_t read(int fildes, void *buf, size_t nbyte) ssize_T write(int fildes, const void *buf, size_t nbyte) en ambas llamadas se debe proporcionar el descriptor del archivo. El segundo parámetro es un buffer, en el caso de read, este buffer será modificado y llenado por la llamada read, con write el buffer solo es leido (por eso la palabra const), y se indica el número de bytes a leer o escribir sobre el archivo. Si la escritura o lectura es exitosa, estas llamadas retornan el número de bytes que se escribieron o leyeron, que pueden ser menor o igual al parametro nbyte. Si retorna negativo, implica un error. El siguiente ejemplo muestra como realizar un programa que copia el contenido de un archivo a otro. /*Ejemplo6 Copia el contenido de un archivo a otro archivo*/ #include #include #define BUFSIZ 128 main(int argc, char *argv[]) { int fd1, fd2; char buffer[BUFSIZ]; /*buffer de lectura y escritura*/ int nbytes; /*contador de bytes leidos*/ if (argc < 3) { /*Para ejecutar este programa se deben pasar dos argumentos de la linea de comando*/ printf("Ejemplo6: Ejemplo6 archivo_origen archivo_destino\n"); } else { /*abrir el archivo origen de solo lectura*/ fd1= open(argv[1],O_RDONLY); if (fd1<0) { perror("Error al abrir el archivo origen"); exit(1); } /*if*/ /*crear al archivo destino*/ fd2= creat(argv[2],0644); if (fd2<0) { perror("Error al crear el archivo destino"); exit(2); } /*if*/ }/*if*/ while ( ( nbytes=read(fd1, buffer, BUFSIZ) ) > 0 ) { printf("Leyendo %d bytes\n",nbytes); nbytes=write(fd2,buffer,nbytes); printf("Escribiendo %d bytes\n",nbytes); }/*while*/ exit(0); } Al correr: Ejemplo6 Ejemplo6.c gus crea un archivo denominado gus. El siguiente ejemplo recibe en la linea de comandos la lista de varios archivos, que son concatenados en un solo. /*Ejemplo7 Concatenar archivos */ #include #include #define BUFSIZ 128 #define MAXFILES 1024 main(int argc, char *argv[]) { int fd[MAXFILES] , fddestino; int narchivos; int i; char buffer[BUFSIZ]; /*buffer de lectura y escritura*/ int nbytes; /*contador de bytes leidos*/ if (argc < 3) { /*Para ejecutar este programa se deben pasar por lo menos dos argumentos de la linea de comando*/ printf("Ejemplo7: Ejemplo7 archivo_origen1 archivo_origen2 ... archivo_destino\n"); exit(1); } else { narchivos= argc-2; for (i=0; i< narchivos;i++) { fd[i]=open(argv[i+1],O_RDONLY); if (fd[i] < 0) { perror("Error al abrir un archivo"); exit(2); } /*if*/ printf("Abriendo archivo %s con descriptor %d\n",argv[i+1],fd[i]); } /*for*/ /*Crear el archivo*/ fddestino=creat(argv[argc-1],0644); if (fddestino < 0) { perror("Error al abrir el archivo destino"); exit(2); } /*if*/ } /*if*/ for (i=0;i 0 ) { printf("Leyendo %d bytes de archivo %d\n",nbytes,fd[i]); nbytes=write(fddestino,buffer,nbytes); printf("Escribiendo %d bytes \n",nbytes); } /*while*/ close(fd[i]); } /*for*/ close(fddestino); exit(0); } Ejercicio: 1. Construir un programa que divida un archivo en varios archivos de un tamaño específico. Desde la linea de comando debe recibir como primer argumento el tamaño de cada archivo y como segundo argumento el nombre del archivo a dividir. + Remover un archivo. unlink. Cuando un archivo no es necesario, se puede remover utilizando la llamada unlink(2), con el siguiente formato: #include int unlink(const char *path); Esta llamada recibe la ruta del archivo a borrar y retorna un numero negativo si existe un error. El siguiente programa se comporta como el comando rm. /*Ejemplo9 Borrar un archivo*/ #include main(int argc, char *argv[]) { int rtn; if (argc==1) { printf("Ejemplo9: Ejemplo9 archivo_a_borrar\n"); exit(1); } rtn=unlink(argv[1]); if (rtn < 0) { perror("Error al tratar de borrar el archivo"); } } Aplicar Ejemplo9 Ejemplo9 debe borrar al mismo programa ejecutable. Pero al tratar un archivo del cual el usuario no es propietario: Ejemplo9 /etc/passwd Error al tratar de borrar el archivo: Permission denied El comando mv de UNIX, mueve el contenido de un archivo a otro. Esto es el equivalente a copiar el archivo origen y luego borrarlo. /*Ejemplo10 Mueve el contenido de un archivo a otro archivo*/ #include #include #define BUFSIZ 128 main(int argc, char *argv[]) { int fd1, fd2; char buffer[BUFSIZ]; /*buffer de lectura y escritura*/ int nbytes; /*contador de bytes leidos*/ int rtn; /*valor retornado por la llamada unlink*/ if (argc < 3) { /*Para ejecutar este programa se deben pasar dos argumentos de la linea de comando*/ printf("Ejemplo6: Ejemplo6 archivo archivo_origen archivo_destino\n"); exit(1); } else { /*abrir el archivo origen de solo lectura*/ fd1= open(argv[1],O_RDONLY); if (fd1<0) { perror("Error al abrir el archivo origen"); exit(2); } /*if*/ /*crear al archivo destino*/ fd2= creat(argv[2],0644); if (fd2<0) { perror("Error al crear el archivo destino"); exit(3); } /*if*/ }/*if*/ while ( ( nbytes=read(fd1, buffer, BUFSIZ) ) > 0 ) { printf("Leyendo %d bytes\n",nbytes); nbytes=write(fd2,buffer,nbytes); printf("Escribiendo %d bytes\n",nbytes); }/*while*/ rtn=unlink(argv[1]); if (rtn<0) { perror("No se puede mover el archivo"); } exit(0); } Tratar Ejemplo10 Ejemplo10 gus Ejercicio. 1. Construir un programa identico al Ejemplo9, pero que reciba una lista de archivos a borrar 2. Por que al mover el archivo Ejemplo10 al archivo gus se perdieron los permisos de ejecucion ? + Acceso directo. lseek. En algunos casos es necesario leer o escribir el archivo en una posicion determinada, como si fuera un arreglo. Es decir, indexar el archivo por una posición. UNIX ofrece la llamada lseek, que tiene el siguiente formato: #include #include off_t lseek(int fildes, off_t offset, int whence) Esta llamada recibe como argumentos el descriptor del archivo, cuantas posiciones se desea mover y a la posicion relativa. Para esta posición relativa: Si es una constante SEEK_SET, la posicion del archivo se pone en offset bytes. Si es una constante SEEK_CUR, la posicion del archivo se pone relativa a la actual mas el offset. Si es una constante SEEK_END, la posición del archivo es puesta al final de archivo mas el offset. El offset puede tomar valores negativos o positivos. Esta llamada retorna el offset resultante (en bytes) o -1 en caso de error. /*Ejemplo12 Uso de offset */ #include #include #include main( int argc, char * argv[]) { int fd, offset,nbytes,i; char buffer[16]; if (argc==2) { printf("Ejemplo9: Ejemplo9 posicion archivo_a_recorrer\n"); exit(1); } else { offset= atoi(argv[1]); } fd= open(argv[2],O_RDONLY); /*Posicionarse offset bytes, con respecto al origen*/ printf("Offset relativo al inicio\n"); offset= lseek(fd,offset,SEEK_SET); if (offset < 0) { perror("Error en lseek SEEK_SET"); exit(2); } nbytes= read(fd,buffer,16); for (i=0;i En posicion 52 esta el caracter En posicion 53 esta el caracter En posicion 54 esta el caracter En posicion 55 esta el caracter Offset relativo al fin de archivo En posicion 1443 esta el caracter f En posicion 1444 esta el caracter s En posicion 1445 esta el caracter e En posicion 1446 esta el caracter t En posicion 1447 esta el caracter + En posicion 1448 esta el caracter i En posicion 1449 esta el caracter , En posicion 1450 esta el caracter b En posicion 1451 esta el caracter u En posicion 1452 esta el caracter f En posicion 1453 esta el caracter f En posicion 1454 esta el caracter e En posicion 1455 esta el caracter r En posicion 1456 esta el caracter [ En posicion 1457 esta el caracter i En posicion 1458 esta el caracter ] En posicion 1459 esta el caracter En posicion 1460 esta el caracter En posicion 1461 esta el caracter En posicion 1462 esta el caracter El siguient ejemplo calcula el tamaño de un archivo /*Ejemplo13 Calcula tamaño de un archivo*/ #include main(int argc, char *argv[]) { int offset,fd; if (argc==1) { printf("Ejemplo13: Ejemplo13 archivo \n"); exit(1); } fd= open(argv[1],O_RDONLY); if (fd<0) { perror("Error al abrir el archivo"); } /*Posicionarse offset bytes, con respecto al origen*/ offset= lseek(fd,0,SEEK_END); if (offset <0 ){ perror("Error al posicionar en fin de archivo"); } printf("Tamanno es %d",offset); close(fd); exit(0); } Ejercicio. 1. Explicar y documentar el Ejemplo14. Dar un ejemplo de uso. #define MAXNOMBRE 128 #define MAXTELEFONO 32 struct telefonos { char nombre[MAXNOMBRE]; char telefono[MAXTELEFONO]; }; extern void inserta(char * archivo,char *nombre, char * telefono); extern void registroEn(char * archivo,long index); #include "Ejemplo14.h" #include #include #include #include #include void inserta(char *archivo,char *nombre, char * telefono) { struct telefonos nuevotelefono; int offset,fd,nbytes; strcpy(nuevotelefono.nombre,nombre); strcpy(nuevotelefono.telefono,telefono); fd= open(archivo,O_WRONLY); if (fd <0 ) { perror("Error al abrir archivo"); exit(3); } offset=lseek(fd,0,SEEK_END); if (offset < 0) { perror("Error al posicionar el archivo"); exit(4); } nbytes=write(fd,&nuevotelefono,sizeof(nuevotelefono)); printf("Agregando %d bytes",nbytes); close(fd); } void registroEn(char * archivo,long index){ int fd, offset,nbytes; struct telefonos registro; fd= open(archivo,O_RDONLY); if (fd <0 ) { perror("Error al abrir archivo o no existe"); exit(3); } offset=lseek(fd, index*sizeof(struct telefonos),SEEK_SET); if (offset < 0) { perror("Error al posicionar el archivo"); exit(4); } nbytes=read(fd,®istro,sizeof(struct telefonos)); if (nbytes < 0) { perror("Error al leer"); exit(5); } printf("Registro %d: Nombre %s Telefono %s\n",index, registro.nombre,registro.telefono); close(fd); } main(int argc,char *argv[]) { char * nombrearchivo=(char *)0; nombrearchivo= getenv("DIRECTORIO"); if (nombrearchivo== (char *) 0) { printf("Se debe definir la variable de ambiente DIRECTORIO"); exit(2); } if (argc == 3) { inserta(nombrearchivo,argv[1],argv[2]); } else if (argc==2) { int pos; pos= atoi(argv[1]); registroEn(nombrearchivo,pos); } else { printf("Ejemplo14: Ejemplo14 nombre telefono | Ejemplo14 #registro\n"); } } 2. Investigar el comando od de UNIX y con este comando examinar el archivo que resulta del ejercicio anterior. 3. Se puede aplicar la llamada lseek sobre la salida estandar ? Probarlo con un programa + Duplicando descriptores de archivos. dup En algunos casos es necesario que un descriptor de archivos sea duplicado. Esto equivale a crear una nueva entrada en la tabla de descriptores de archivos del proceso y que tiene los mismos atributos del archivo a partir del cual fue duplicado. La llamada que realiza esto es: #include int dup(int fildes); Esta llamada solo recibe como parametro de entrada el descripotr de archivo que se desea duplicar y retorna el descriptor de archivo que es el duplicado del descriptor de archivo fildes. /*Ejemplo15 Muestra la utilizacion de dup para manejo de archivos*/ #include #include main(int argc, char * argv[]) { char * nombrearchivo; int fd,fd2; if (argc==1) { printf("Ejemplo1: proporcionar un nombre de archivo\n"); exit(1); } else { nombrearchivo=argv[1]; } /*if*/ /*crear con permisos de lectura y escritura para todos*/ fd = open(nombrearchivo, O_RDONLY); if (fd < 0) { perror("Checar la ruta ,permisos del directorio o el archivo si existe"); exit(2); } else { printf("El descriptor del archivo es %d\n", fd); /*ahora invocar dup*/ fd2=dup(fd); printf("El duplicado del descriptor del archivo es %d\n", fd2); printf("El descriptor %d tiene offset %d\n", fd, lseek(fd,0,SEEK_SET)); printf("El descriptor %d tiene offset %d\n", fd2, lseek(fd2,0,SEEK_END)); close(fd); close(fd2); exit(0); } } Al correrlo: Ejemplo15 Ejemplo15.c El descriptor del archivo es 3 El duplicado del descriptor del archivo es 4 El descriptor 3 tiene offset 0 El descriptor 4 tiene offset 824 Se puede observar que al tener el duplicado, se pueden realizar operaciones independientes sobre el archivo, en este caso, ponerse al inicio y al final del archivo, pero con diferentes descriptores. El siguiente ejemplo, muestra el uso de dup para un proceso hijo y padre. /*Ejemplo16*/ #include #include main(int argc, char * argv[]) { char * nombrearchivo; int fd,fd2; int pid; char buffer1[]="hola papa\n"; char buffer2[]="adios hijo\n"; if (argc==1) { printf("Ejemplo1: proporcionar un nombre de archivo\n"); exit(1); } else { nombrearchivo=argv[1]; } /*if*/ /*crear con permisos de lectura y escritura para todos*/ fd = creat(nombrearchivo, 0644); if (fd < 0) { perror("Checar la ruta ,permisos del directorio o el archivo si existe"); exit(2); } else { printf("El descriptor del archivo es %d\n", fd); /*ahora invocar dup*/ fd2=dup(fd); printf("El duplicado del descriptor del archivo es %d\n", fd2); pid=fork(); if (pid==0) { int nbytes; close(fd); nbytes=write(fd2, buffer1,strlen(buffer1)); printf("Escribiendo %d \n",nbytes); printf("El descriptor %d tiene offset %d\n", fd2, lseek(fd2,0,SEEK_CUR)); close(fd2); exit(0); } else { int status; close(fd2); /*esperar al hijo*/ wait(&status); /*avanzar la longitud del buffer1 para no encimar lo escrito por el hijo*/ lseek(fd,strlen(buffer1),SEEK_SET); write(fd, buffer2,strlen(buffer2)); printf("El descriptor %d tiene offset %d\n", fd, lseek(fd,0,SEEK_CUR)); close(fd); exit(0); } } } Es necesario observar que se realizo un corrimiento sobre el archivo para que el proceso padre no encimara el contenido del archivo generado por el hijo. Ejercicios. 1. Modificar el ejemplo16 y no aplicar el corrimiento que efectua el padre. Que sucede ? Directorios. Un directorio es un archivo que contiene referencias a otros archivos. Este tipo de archivo solo puede ser escrito por el kernel, por lo tanto no existen llamadas directas para modificarlo, sin embargo, si es posible leer su contenido o navegar a través de un directorio. Un directorio queda definido por su ruta. Sin embargo, un directorio contiene como información, una lista de archivos y sus inodos correspondientes. Sobre un directorio se pueden aplicar las siguientes operaciones: + Creacion de un directorio. mkdir. La llamada mkdir crea un nuevo directorio. Cuando un directorio es creado, tiene como contenido dos referencias, la primera es al directorio padre o "..", y la segunda es la referencia a el mismo o ".". La llamada mkdir tiene el siguiente formato #include #include int mkdir(char * path, mode_t mode) El argumento path indica la ruta del directorio y el argumento mode indica los permisos del directorio (lectura, escritura, ejecucion). Si el directorio es creado, retorna un 0, de lo contrario un valor negativo. Las razones por las cuales puede fallar la creacion de un directorio son debidas a falta de permisos o el directorio existe. El siguiente ejemplo muestra la creacion de varios directorios, con distintos permisos. /*Ejemplo19*/ #include #include main() { /*Crear un directorio con permisos de lectura, escritura y ejecucion para el propietario*/ int rtn,fd; rtn=mkdir("dir1",0700); if (rtn < 0) { perror("Error al crear dir1"); exit(1); } /*Crear un directorio con permisos de lectura, escritura para el propietario*/ rtn=mkdir("dir2",0600); if (rtn < 0) { perror("Error al crear dir2"); exit(1); } /*Ahora crear archivos sobre los directorios*/ fd= creat("dir1/archivo1",0644); if (fd < 0) { perror("Error al crear dir1/archivo1"); exit(1); } close(fd); rtn=mkdir("dir1/subdir1",0700); if (rtn < 0) { perror("Error al crear dir1/subdir1"); exit(1); } fd= creat("dir2/archivo1",0644); if (fd < 0) { perror("Error al crear dir2/archivo1"); exit(1); } exit(0); } Al correr Ejemplo19 Error al crear dir2/archivo1: Permission denied Esto se debe a que el directorio dir2 fue creado con permisos 0644, el permiso de ejecucion se suprimio, implicando esto que no es posible escribir archivos sobre este directorio o cambiarse a este mismo. Si se trata de correr Ejemplo19, debe fallar, debido a que los directorios ya estan creados. + Cambiando el directorio actual. chdir y fchdir. Estas llamadas al kernel permiten que el proceso que las ejecuta, cambie su directorio de referencia y los archivos que proporcione con ruta relativa, son creados con respecto al directorio actual. Las llamadas tienen el siguiente formato: #include int chdir(const char * path) int fchdir(int fd) La primera llamada, chdir, dada una ruta, le indica al kernel que el directorio actual de trabajo es el referenciado por path. Si el cambio de directorio es correcto, retorna 0. En el caso de la llamada fchdir, recibe un descriptor de archivo, resultado de una llamada open. El siguiente ejemplo muestra el uso de estas llamadas. #include #include #include #include /* Ejemplo20 Uso de chdir y fchdir */ main() { int rtn,fd; /*cambiarse al directorio /tmp*/ rtn= chdir("/tmp"); if (rtn < 0) { perror("Error al cambiarse al directorio /tmp"); exit(1); } /*Crear un subdirectorio dentro de /tmp*/ rtn= mkdir("mio",0700); if (rtn < 0) { perror("Error al crear al directorio /tmp/mio"); exit(2); } /*Cambiarse al directorio /tmp/mio, utilizando open y fchdir*/ fd=open("mio",O_RDONLY); if (fd < 0) { perror("Error al abrir el directorio /tmp/mio"); exit(3); } rtn=fchdir(fd); if (rtn < 0) { perror("Error al crear al cambiarse al directorio /tmp/mio"); exit(4); } fd= creat("archivo1",0644); if (fd < 0) { perror("Error al crear el archivo /tmp/mio/archivo1"); exit(5); } /*concluir con un listado recursivo de /tmp/mio*/ rtn=execl("/bin/ls","/bin/ls","-lR",(char *)0); if (rtn < 0) { perror("Error al ejecutar ls"); exit(6); } exit(0); } Al correr este ejemplo: Ejemplo20 .: total 0 -rw-r--r-- 1 gus other 0 MMar 10 23:21 archivo1 Si se trata de ejecutar, debe marcar un error, ya que el directorio mio ya existe. Se puede observar que un archivo puede ser manipulado como un archivo a traves de la llamada open. Por ejemplo, correr el Ejemplo6 : Ejemplo6 /tmp/mio /tmp/mio2 Sin embargo, este programa no es del todo consistente, ya que el archivo /tmp/mio2 lo deja en 0 bytes y no como directorio, debido que mio2 es creado con una llamada creat y no mkdir. + Borrado de un directorio. rmdir. Un directorio se puede borrar con la llamada rmdir. #include int rmdir(const char * path) Recibe la ruta del directorio a borrar. Si tiene exito, retorna 0, de lo contrario un valor negativo. Por ejemplo, si el directorio contiene archivos, no puede borarlo. #include /*Ejemplo21 Borrardo de un directorio*/ main() { int rtn; rtn= rmdir("/tmp/mio"); if (rtn < 0) { perror("No se puede borrar el directorio /tmp/mio"); exit(1); } exit(0); } + Lectura de directorios Para leer un directorio ¿ se podría utilizar la llamada read ? El siguiente ejemplo lo muestra: /* Ejemplo22 */ #include main() { int fd,nbytes; char buf[128]; fd= open("/tmp",O_RDONLY); if (fd < 0) { perror("No se puede abrir el directorio /tmp"); exit(1); } while ( ( nbytes= read(fd,buf,128) ) > 0) { printf("Leyendo %d \n",nbytes); write(1,buf,nbytes); } if (nbytes < 0) { perror("Error al leer el directorio"); exit(1); } exit(0); } Ejemplo22 Error al leer el directorio: Invalid argument Esto sucede ya que el archivo /tmp es un directorio y no se puede leer facilmente. Para esto, UNIX proporciona funciones de C para realizarlo, que oculta al programador la tarea de bajo nivel. Estas funciones son las siguientes: #include #include DIR * opendir(const char *dirname) struct dirent * readdir(DIR *dirp): int closedir(DIR *dirp) donde DIR y struct dirente son estructuras de datos para representar, respectivamente, un directorio y la entrada de cada directorio. La entrada de cada directorio, tiene un atributo denominado d_name, que permite saber cual es el nombre del archivo. El siguiente ejemplo muestra como leeor el contenido de un directorio. #include #include main(int argc,char *argv[]) { DIR * directorio; struct dirent *direntp; int rtn; if (argc ==1) { printf("Ejemplo23: Ejemplo23 directorio\n"); exit(1); } directorio=opendir(argv[1]); if (directorio == (DIR *) 0) { perror("Error al abrir el directorio"); exit(2); } while ( ( direntp=readdir(directorio) ) != (struct dirent *) 0 ) { printf("Archivo %s \n",direntp->d_name); } rtn=closedir(directorio); if (rtn< 0) { perror("Error al cerrar el directorio"); exit(3); } exit(0); } Ejercicios: + Construir los comandos cd, mkdir y rmdir utilizando las llamadas anterorimente utilizadas. + Construir un comando lsdir, que liste un directorio. + Lectura de inodos. stat y fstat. En algunos casos es necesario leer de forma directa el inodo de un archivo, ya sea para saber el tipo de archivo, tamaño, propietarios. La llamada stat y fstat permite realizar esto, su formato es: #include #include int stat(const char* path, struct stat * buf) int fstat(int fd, struct stat * buf) En una función se accesa el archivo con la ruta y en otra con el descriptor de archivo. La estructura struct stat contiene campos con informacion acerca del archivo y el manual indica estos: mode_t st_mode; /* File mode (see mknod(2)) */ ino_t st_ino; /* Inode number */ dev_t st_dev; /* ID of device containing */ /* a directory entry for this file */ dev_t st_rdev; /* ID of device */ /* This entry is defined only for */ /* char special or block special files */ nlink_t st_nlink; /* Number of links */ uid_t st_uid; /* User ID of the file's owner */ gid_t st_gid; /* Group ID of the file's group */ off_t st_size; /* File size in bytes */ time_t st_atime; /* Time of last access */ time_t st_mtime; /* Time of last data modification */ time_t st_ctime; /* Time of last file status change */ /* Times measured in seconds since */ /* 00:00:00 UTC, Jan. 1, 1970 */ long st_blksize; /* Preferred I/O block size */ blkcnt_t st_blocks; /* Number of 512 byte blocks allocated*/ El campo st_mode permite saber de que tipo es el archivo, siendo los siguiente tipos los utilizados por UNIX S_IFIFO fifo special S_IFCHR character special S_IFDIR directory S_IFBLK block special S_IFREG ordinary file Tambien st_mode contiene la mascara de permisos. Para poder obtenerla, se debe manipular con operaciones de bytes. Si estas llamadas fallan, retornan un numero negativo, de lo contrario 0. El siguiente programa muesta el uso de esta llamada. #include #include void informacionarchivo(const char * archivo) { int rtn; struct stat buf; rtn=stat(archivo,&buf); if (rtn< 0) { perror("Error en stat"); exit(1); } printf("%s\n",archivo); printf("Modo %x\n",buf.st_mode&0xF000); printf("Permisos %o\n",buf.st_mode&0x1FF); printf("Inodo %d\n",buf.st_ino); printf("ID Device %d\n",buf.st_dev); printf("Numero de referencias %d\n",buf.st_nlink); printf("Propietario %d\n",buf.st_uid); printf("Grupo %d\n",buf.st_gid); printf("Tamanno %d\n",buf.st_size); printf("Ultimo acceso %d\n",buf.st_atime); printf("Ultima modificacion %d\n",buf.st_mtime); printf("Ultimo cambio %d\n",buf.st_ctime); printf("Tamanno bloque %d\n",buf.st_blksize); printf("Numero bloques %d\n",buf.st_blocks); } main() { int rtn; struct stat buf; informacionarchivo("/etc/passwd"); informacionarchivo("/tmp"); informacionarchivo("/dev/console"); exit(0); } Ejemplo24 /etc/passwd Modo 8000 Permisos 444 Inodo 7652 ID Device 8388632 Numero de referencias 1 Propietario 0 Grupo 3 Tamanno 530 Ultimo acceso 921379602 Ultima modificacion 920231947 Ultimo cambio 920231947 Tamanno bloque 8192 Numero bloques 2 /tmp Modo 4000 Permisos 777 Inodo 515014614 ID Device 1 Numero de referencias 5 Propietario 3 Grupo 3 Tamanno 280 Ultimo acceso 921379573 Ultima modificacion 921380400 Ultimo cambio 921380400 Tamanno bloque 4096 Numero bloques 8 /dev/console Modo 2000 Permisos 620 Inodo 3968 ID Device 8388632 Numero de referencias 1 Propietario 1001 Grupo 7 Tamanno 0 Ultimo acceso 921379629 Ultima modificacion 921379641 Ultimo cambio 921379571 Tamanno bloque 8192 Numero bloques 0 Ejercicios. 1. Explicar cada campo de la estructura stat. 2. Escribir un programa que reciba como argumento el tipo de un archivo e indique que tipo de archivo es. (Archivo normal, directorio, orientado a caracteres o por bloques) (Nota, investigar las macros definidas en /usr/include/sys/stat.h). 3. Construir un comando similiar a ls -l, utilizando la llamada stat. Debe mostrar permisos, numero de referencias, propietario, grupo, tamaño, tiempo de ultima modificación y nombre del archivo. (Considerar que los permisos se deben poner en formato de cadena y no en octal, e investigar si es posible realizar la impresion de la fecha en formato MM-dd-YYYY) 4. Dado que las fechas de acceso de los archivos estan representadas por un numero long con signo, explicar en maquinas de 32 bits en que año tendría problemas UNIX. 5. Modificar los ejemplos que copian y mueven un archivo, para que el nuevo archivo creado tenga el mismo esquema de permisos que el archivo orígen. + Ligas. En UNIX es posible tener varias referencias a un archivo, dando diferentes nombres al archivo. Esto se conoce como una liga o enlace. Una liga puede ser de tipo dura o suave. Un liga dura hace referencia el inodo de un archivo. Por ejemplo, suponiendo el archivo /home/alumno/cib61210/tema4.taz, es posible tener referencia a este archivo con el nombre misapuntes.taz, dando el comando: ln /home/alumno/cib61210/tema4.taz misapuntes.taz Y se puede evitar la copia del archivo a su directorio hogar. Si en un momento el archivo tema4.taz se remueve de la cuenta /home/alumno/cib61210, aun no se pierde, ya que existe la referencia misapuntes.taz. Una liga dura es una entrada mas a un directorio, que apunta al inodo del archivo que se indico en el comando ln. Una liga dura, no puede hacerse entre diferentes sistemas de archivos. Una liga suave o simbica es aquella que apunta a un archivo, utilizando el nombre del archivo en lugar de un inodo. Por ejemplo, para referencia al directorio /tmp como temporal, dentro del directorio de trabajo, se puede utilizar una liga suave. ln -s /tmp $HOME/temporal Ejercicio. 1. Investigar y dar un ejemplo de las llamadas: link(2) symlink(2) readlink(2) lstat(2) 2. Construir dos comandos, uno que cree una liga dura y otro que cree una liga simbólica. 3. Construir un ejemplo que permita identificar la diferencia entre la llamada stat y lstat. + Redireccionamiento. En UNIX cada proceso tiene un tabla de descriptores de archivos. Cada descriptor de archivos es un apuntador a la tabla del sistema de archivos. Se puede provocar que una o mas entradas de la tabla de descriptores apunte al mismo archivo. Por ejemplo, la entrada estándar, que normalmente es el teclado, puede modificarse y hacer que apunte a un archivo diferente. O la salida estándar, en lugar de realizar la escritura de sus resultados a la pantalla, la realiza sobre otro archivo. Esto se conoce como redireccionamiento. En los shells de UNIX, normalmente se aplica el redireccionamiento con los caracteres >, <, >>, <<. Por ejemplo: cat < /etc/passwd > passwords Para entender esta expresion, primero correr el comando cat: cat este comando parece que se queda esperando, pero al escribir algo Programacion de sistemas seguido de un salto de carro, manda a pantalla el mensaje, programacion de sistemas. Ahora, si se ejecuta: cat < /etc/passwd root:x:0:1:Super-User:/:/sbin/sh daemon:x:1:1::/: bin:x:2:2::/usr/bin: sys:x:3:3::/: adm:x:4:4:Admin:/var/adm: lp:x:71:8:Line Printer Admin:/usr/spool/lp: smtp:x:0:0:Mail Daemon User:/: uucp:x:5:5:uucp Admin:/usr/lib/uucp: nuucp:x:9:9:uucp Admin:/var/spool/uucppublic:/usr/lib/uucp/uucico listen:x:37:4:Network Admin:/usr/net/nls: nobody:x:60001:60001:Nobody:/: noaccess:x:60002:60002:No Access User:/: nobody4:x:65534:65534:SunOS 4.x Nobody:/: gus:x:1001:1::/opt/gus:/bin/csh oracle:x:1002:101:Cuenta ORACLE:/opt/oracle:/bin/csh Muestra el contenido del archivo. En este caso, la entrada no la toma del teclado, sino que, utiliza el operador < que indica que debe tomar la entrada del archivo /etc/passwd Ahora cat < /etc/passwd > passwords no genera ninguna salida en pantalla. Pero al checar el contenido del archivo passwords, se puede ver que contiene la informacion de usuarios, es decir, en lugar de enviar a pantalla, lo envio al archivo passwords. Para realizar este tipo de operacion desde un programa, se puede aplicar una llamada denominada dup2 dup2(3C) #include int dup2(int fildes,int fildes2) Esta llamada es parecida a la llamada dup, pero en lugar de copiar el descriptor de archivo fildes2 a un nuevo descriptor de archivos, lo copia a un descriptor ya existente, fildes. El siguiente ejemplo lee la entrada estandar a partir de un archivo indicado. /*Ejemplo25*/ #include #include #include #include main(int argc,char * argv[]) { int fd,fd2; int nbytes; char buffer[128]; if (argc==1) { printf("Ejemplo25: Ejemplo25 archivolectura"); exit(1); } /*Abrir el archivo indicado*/ fd = open(argv[1],O_RDONLY); /*Duplicar la entrada estándar en el archivo referenciado por fd*/ fd2=dup2(fd,0); if (fd2 < 0) { perror("No es posible redireccionar la entada"); exit(2); } /*Aqui debe mostrar un 0*/ printf("%d\n",fd2); /*Leer todo el archivo como si fuera el teclado*/ while ( ( nbytes = read(0,buffer,sizeof(buffer)) ) > 0 ) { write(1,buffer,nbytes); } close(fd); exit(0); } Por ejemplo Ejemplo25 /etc/passwd 0 root:x:0:1:Super-User:/:/sbin/sh daemon:x:1:1::/: bin:x:2:2::/usr/bin: sys:x:3:3::/: adm:x:4:4:Admin:/var/adm: lp:x:71:8:Line Printer Admin:/usr/spool/lp: smtp:x:0:0:Mail Daemon User:/: uucp:x:5:5:uucp Admin:/usr/lib/uucp: nuucp:x:9:9:uucp Admin:/var/spool/uucppublic:/usr/lib/uucp/uucico listen:x:37:4:Network Admin:/usr/net/nls: nobody:x:60001:60001:Nobody:/: noaccess:x:60002:60002:No Access User:/: nobody4:x:65534:65534:SunOS 4.x Nobody:/: gus:x:1001:1::/opt/gus:/bin/csh oracle:x:1002:101:Cuenta ORACLE:/opt/oracle:/bin/csh Tambien se puede redireccionar la salida estándar a un archivo: Ejemplo26 salida hola mundo adios mundo ^D el archivo salida contiene el texto que se tecleo y ademas la impresion del descriptor de archivo fd2, que es igual a 1. Por ultimo, el siguiente ejemplo, leer de un archivo y envia a otro archivo la salida estandar #include #include #include #include main(int argc,char * argv[]) { int fdin,fdout,fd1,fd2; int nbytes; char buffer[128]; if (argc<=2) { printf("Ejemplo25: Ejemplo25 archivolectura archivoescritura"); exit(1); } /*Abrir los archivo indicado*/ fdin = open(argv[1],O_RDONLY); if (fdin< 0) { perror("No es posible leer el archivolectura"); exit(2); } fdout= creat(argv[2],0644); if (fdout< 0) { perror("No es posible escribir al archivoescritura"); exit(3); } /*duplicar la entrada estandar en el descriptor fdin*/ fd1= dup2(fdin,0); if (fd1 < 0) { perror("No es posible redireccionar la entrada estandar"); exit(4); } /*duplicar la salida estandar en el descriptor fdout*/ fd2=dup2(fdout,1); if (fd2 < 0) { perror("No es posible redireccionar la entada"); exit(5); } while ( ( nbytes = read(0,buffer,sizeof(buffer)) ) > 0 ) { write(1,buffer,nbytes); } exit(0); } + Lectura/Escritura con o sin bloqueo. Cuando se lee de entrada estandar, al realizar la llamada read, esta se queda en espera de que se introduzca algo en el teclado, es decir, la llamada read se bloquea hasta que se introduce algo del teclado. Se puede cambiar este comportamiento utilizando la llamada fcntl. #include #include #include int fcntl(int fildes, int cmd, /* arg */ ...); El primer argumento es el descriptor de archivos, el segundo un comando que se aplica sobre el archivo y los parámetros restantes, los argumentos para dicho comando. El valor de retorno depende del comando que se indique, pero si es negativo, es un error. Uno de esos comandos es aquel que pide las banderas de estado del archivo. Este comando es F_GETFL, con valor de tercer parametro igual a 0 y que retorna las banderas. Sobre estas banderas se pueden aplicar operaciones de bits, y el estado del archivo se puede cambiar con el comando F_SETFL. El siguiente ejemplo muestra el uso de fcntl. /*Ejemplo28 -- Uso de fcntl */ #include #include #include #include #include main(int argc,char * argv[]) { int fd,fd2; int nbytes; char buffer[128]; int fd_flags; int rtn; /*Conoceer las banderas del archivo*/ fd_flags= fcntl(0, F_GETFL,0); printf("flags %x\n",fd_flags&O_ACCMODE); if (fd_flags<0) { perror("Error en fcntl"); exit(3); } /*Indicar que no se bloquee ante un read*/ fd_flags|=O_NONBLOCK; printf("flags %x\n",fd_flags&O_ACCMODE); fcntl(0,F_SETFL, fd_flags); if (rtn < 1) { perror("No puedo poner nuevas banderas"); } while ( 1 ) { char cmd[128]; int nbytes; /*No se espera a que se le teclee algo*/ nbytes=read(0,cmd, 128); if ( nbytes >0) { write(1,cmd,nbytes); exit(0); }else { printf("Teclear algo para salir\n"); } } } Ejercicio. 1. Investigar como implantar la llamada dup y dup2 utilizando fcntl. 2. Dar un ejemplo de redireccionamiento utilizando fcntl. 3. Por que al correr el Ejemplo28, y teclear el comando cat sin opciones, marca error ? + Archivos especiales. Dispositivos de caracteres En UNIX los dispositivos de entrada son manejados por medio de los manejadores de dispositivos o devices drivers. Un manejador de dispositivo es cargado en el kernel de UNIX y se encarga de interactuar con el dispositivo fisico. Un usuario puede comunicarse con un manejador de dispositivos utilizando las llamadas de manipulacion de archivos. Los dispositivos estan localizados en el directorio /dev. + Dispositivos especiales de caracteres. STREAMS. Cuando un usuario quiere configurar o leer ciertos parametros del manejador de dispositivos tiene que utilizar STREAMS o flujos. El concepto de STREAMS se refiere a un conjunto de definiciones sobre como contorlar a los manejadores de dispositivos. Cada manejador de dispositivos espera recibir una instruccion o mensaje a partir de una llamada que utiliza STREAMS. ------- -------- --------------------- Usuario --Llamadas E/S-- STREAMS--- Manejador Dispositivo--- Dispositivo ------- -------- --------------------- El usuario se comunica con lo que se conoce como el "encabezado" de un STREAM, y el encabezado delega el mensaje del usuario al modulo que le corresponda. El modulo de procesamiento se encarga de interpretar los mensajes y los que no entiende los envia al modulo que este abajo de el. El modulo inferior interpreta los mensajes y al final, el modulo colocado hasta el final, se encarga de interactuar con el dispositivo. Este diseño permite que la E/S se puede procesar por modulos. Para poder interactuar con STREAMS, es necesario identificar si el dispositivo de caracteres es un STREAM. La llamada isastream(3C) indica este hecho: #include int isastream(int fildes) Recibe el descriptor de archivo del dispositivo. Esta llamada retorna un valor 0 si no es un STREAM, 1 si es un STREAM y un numero negativo si existe un error. En caso de que el dispositivo sea un STREAM, se puede utilizar la llamada ioctl(2), que para el caso de STREAMS, tiene el siguiente formato #include #include int ioctl(int fildes, int request, /*arg*/ ...) Recibe el descriptor de archivos, un numero que indica una petición hacia el dispositivo y que VARIA por cada manejador de dispositivo y un conjunto de argumentos, que varían en tipo y número dependiendo de la petición. Es posible identificar los módulos de procesamiento de STREAMS de un manejador de dispositivos, utilizando la llamada ioctl. El siguiente ejemplo lo muestra. /*Muestra el uso de STREAMS*/ #include #include #include #include void informacionStreams(int fd) { int n_mods,i; struct str_list modulos; /*Checar si es un stream*/ if (isastream(fd)) { printf("es una archivo de STREAMS\n"); } else return; /*Obtener el numero de modulos Se utiliza ioctl sobre el descriptor de archivos, la peticion I_LIST indica que se desea conocer cuantos modulos de STREAMS estan dentro de este archivo, no se envia ningun argumento */ n_mods=ioctl(fd,I_LIST, NULL); if (n_mods < 0) { perror("No puedo contar modulos"); exit(3); } printf("Numero Modulos %d\n",n_mods); /* La estructura str_list contiene la lista de cada modulo, este tiene esta organizada asi: struct str_list { int sl_nmods; struct str_mlist *sl_modlist; }; y la estructura str_mlist struct str_mlist { char l_name[FMNAMESZ+1]; }; */ /*Llenar la estructura indiando el numero de modulos*/ modulos.sl_nmods=n_mods; /*y pasando el buffer de la lista de modulos*/ modulos.sl_modlist= ( void *)calloc(n_mods,sizeof(struct str_mlist)); /*ioctl con peticion I_LIST y estrutura str_list indica todos los modulos */ ioctl(fd,I_LIST,&modulos); /*Imprimir cada modulo*/ for (i=0;i #include main(int argc,char * argv[]) { int fd; int rtn; struct sgttyb configuracion; if (argc==1) { printf("Ejemplo30: Ejemplo30 archivo"); exit(1); } fd=open(argv[1],O_RDONLY); if (fd < 0) { perror("No puedo abrir el archivo"); exit(2); } if (isastream(fd)) { printf("%s es una archivo de STREAMS\n",argv[1]); }else { exit(3); } /*Para manejar terminal, el modulo ttcompat recibe la peticion TIOCGETP, que retorna llena la estructura sgttyb con los siguiente campos struct sgttyb { char sg_ispeed; /* input speed */ char sg_ospeed; /* output speed */ char sg_erase; /* erase character */ char sg_kill; /* kill character */ int sg_flags; /* mode flags */ }; */ rtn=ioctl(fd,TIOCGETP,&configuracion); if (rtn<0) { perror("Error en ioctl TIOCGETP"); exit(4); } printf("Configuracion actual de la terminal %s",argv[1]); printf("sg_ispeed %x ", configuracion.sg_ispeed); printf("sg_ospeed %x ", configuracion.sg_ospeed); printf("erase %x ", configuracion.sg_erase); printf("kill %x\n ", configuracion.sg_kill); /*Poner los caracteres de borrado y eliminacion a z y w*/ configuracion.sg_erase='z'; configuracion.sg_kill='w'; /*Enviar la peticion al modulo STREAM ttcompat, con la peticion TIOCSETP*/ rtn=ioctl(fd,TIOCSETP,&configuracion); if (rtn<0) { perror("Error en ioctl TIOCSETP"); exit(4); } close(fd); exit(0); } Al ejecutar Ejemplo30 /dev/pts/3 y despues de esto, para borrar un caracter, se debe utilizar una z. Ejercicio. 1. Investigar mas sobre STREAMS en el libro Advanced Programming in the UNIX environment de Stevens. No confundir con el concepto de streams, que se refiere a las llamadas de tipo fopen, flcose, etc. 2. Investigar el comando stty. 3. Investigar como configurar el caracter de interrrupcion y fin de archivo utilizando STREAMS. 4. Explicar como se implanta TCP/IP en UNIX utilizando STREAMS. 5. En el libro de texto, en el punto 3.14 se habla del control de terminal no utilizando STREAMS. Tratar de implantar el Ejemplo30 con estas llamadas. EJEMPLO FINAL. Buscando la ocurrencia de un patrón dentro de un conjunto de directorios. /*Ejemplo31*/ #include #include #include #include #include void tienepatron(char *directorio,char * archivo, char * patron) { int pid,rtn; pid= fork(); if (pid< 0) { perror("Error en fork"); return; } if (pid==0) { rtn= execl("/bin/grep","/bin/grep",patron,archivo,(char *) 0); if (rtn< 0) { perror("Error en grep"); exit(2); } } else { waitpid(pid,(int *) 0, 0); } } void busca(char * directorio,char *patron) { DIR * direc; struct dirent * entrada; char * nombreentrada; struct stat buf; char * actual; int rtn; printf("Directorio %s\n",directorio); direc=opendir(directorio); if (direc == (DIR *) 0) { printf("No se puede manipular %s \n",directorio); return; } actual= getcwd((char *) 0, 1024); if (actual == (char *) 0) { perror("Error al obtener directorio actual"); return; } rtn= chdir(directorio); if (rtn < 0) { perror("chdir"); exit(1); } while ( (entrada=readdir(direc) ) != (struct dirent *) 0 ) { nombreentrada=entrada->d_name; if ( !strcmp(nombreentrada,".") || !strcmp(nombreentrada,"..") ) continue; printf("%s\n",nombreentrada); stat(nombreentrada,&buf); if ( (buf.st_mode&0xF000) == 0x4000 ) { busca(nombreentrada,patron); } else { tienepatron(directorio,nombreentrada,patron); } } closedir(direc); rtn= chdir(actual); if (rtn < 0) { perror("chdir"); exit(1); } return; } main(int argc, char *argv[]) { char *directorio; char * patron; if (argc<3) { printf("Ejemplo31 patron directorio"); } patron= argv[1]; directorio=argv[2]; busca(directorio,patron); } EJERCICIOS. 1. Entender el programa 31 y documentarlo. 2. Que optimizaciones se pueden hacer sobre el ejemplo 31. Checar la optimizacion utilizando el comando time. Encontrar el comando equivalente al Ejemplo 31 y dar un ejemplo de como usuarlo. (nota, una opcion de optimizacion puede ser el no generar un nuevo proceso y un exec al buscar el patron, pensar en construir una funcion que busque en un archivo un patron dado). 3. Implantar el ejercicio 3.11 del libro de texto. 4. Implantar el ejercicio 3.12 del libro de texto. (nota se debe utilizar Solaris o Linux). 5. En UNIX existen varias herramientas para procesar el contenido de un archivo, investigar y dar un ejemplo de: sed grep cut paste sort awk (Investigar en el libro "El Entorno de Programacion UNIX" de Kernighan). 6. Investigar el uso del comando tar. Como se realizan respaldos en UNIX utilizando cintas magnéticas ? 7. Comparar manejo de archivos de NT y UNIX. 8. Leer el capitulo 3 y 5 del libro "Sistemas Operativos. Diseño e Implantación" de Tanenbaum y explicar: + Manejo de Hardware E/S. + Software de E/S. + Manejadores de dispositivos en Minix + Administración de discos y terminales. + Sistema de archivos en MINIX. 9. Investigar el uso del floppy en el sistema UNIX donde se realice la práctica.