 	Programacin de Sistemas.

	Tema IV
	Archivos.

+ Entrada y Salida en UNIX.

UNIX fue diseado para soportar la entrada y salida de una manera uniforme.

Para esto, UNIX tiene como abstraccin 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 perifrico 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 tpicas 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 informacin.


+ Organizacin del sistema de archivos.

UNIX decidi implantar su sistema de archivos utilizando un mdelo 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 estn 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 especfico,
y para poder manejar cada nodo, debe proporcionarse una ruta. 

El ndo inicial de la jerarqua no tiene nombre alguno y es conocido como
el directorio raz (root). Para manipularlo, se hace referencia a l dando
la ruta, que es /.

Este nodo raz 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)
	
+ Representacin del sistema de archivos en UNIX.

UNIX no concibe los discos como volmenes o unidades de disco. 
Para poder accesar a un disco, este se debe particionar. Al crear una 
particin, se utilizan las herramientas para dar formato a un disco.

Sobre cada particin se puede construir un sistema de archivos. Esta
creacin se realiza una vez, normalmente al instalar al disco.

La construccin 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 manipulacin de los archivos.

Cada particin, al tener un sistema de archivos, es "montada" o anexada
al rbol del sistema de archivos. Gracias a esto, UNIX puede crecer sin limite
(tericamente) en el nmero de sistemas de archivos que puede manejar
una computadora, ya que puede incorporar sistemas de archivos de varios
discos o an mas, de diferentes sistemas de cmputo.

La estructura de datos principal es conocida como un inodo. Un inodo contiene
informacin asociada con el archivo, tal como, el tamao del archivo, 
el identificador del usuario y grupo a quien pertenece este archivo, algunas
fechas relacionadas con el archivo, permisos y una relacin o cuenta de
quien tiene referencias a este archivo. Adems 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 relacin de apuntadores permite que el
archivo crezca a tamaos de 2GB o ms dependiendo del sistema operativo.

Cuando se crea un sistema de archivos, se crean los inodos que se pueden 
utilizar (con respecto al tamao 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 informacin del disco.

Para optimizar el acceso a los archivos, UNIX tiene en un cache los inodos
que recientemente se han utilizado. Despus de un tiempo, si algn 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 informacin
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 nmero 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 omisin todo proceso
tiene tres descriptores de archivos asociados, entrada estndar, salida 
estndar y error estndar, con nmero 0, 1 y 2 respectivamente.

Ejercicios.
1. Ejecutar el comando mount, y checar las particiones del disco de
la estacin 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 informacin 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
		+ Creacin de un directorio.
		+ Destruccin de un directorio.
		+ Obtener informacin sobre un directorio.
		+ Cambiarse a un directorio especfico.

	Archivos ordinarios.
		+ Creacin.
		+ Apertura.
		+ Borrado.
		+ Obtener informacin.
		+ Lectura y escritura.
		+ Posicionamiento.
		+ Cerrado.
	
	Archivos especiales.
		+ Apertura.
		+ Obtener informacin sobre un dispositivo.
		+ Configuracin de un dispositivo.

Como en UNIX todos los archivos son tratados de forma idntica, 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, creacin, lectura/escritura o truncar. Opcionalmente,
open puede recibir un tercer parmetro que indica los permisos con los
que se desea accesar el archivo.

El formato de open es:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *path, int oflag, /*mode_t mode*/)

El valor oflag puede tomar los siguientes valores, definidos en <fcnlt.h>

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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 nmero negativo. Si el archivo exista
lo destruye si y solo si se tienen permisos.

El siguiente ejemplo muestra la creacion de un archivo.

/*Ejemplo3 Crear un archivo*/

#include <sys/stat.h>
#include <fcntl.h>

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 <unistd.h>
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 <unistd.h>

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 <unistd.h>

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 operacin de lectura y/o escritura. 
Para realizar esto, se utiliza la llamada read(2) y write(2) con el siguiente
formato:

#include <unistd.h>
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 parmetro 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 nmero de bytes a leer o escribir sobre
el archivo. Si la escritura o lectura es exitosa, estas llamadas retornan
el nmero 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 <unistd.h>
#include <sys/fcntl.h>

#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 <unistd.h>
#include <sys/fcntl.h>

#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<narchivos;i++) {

		int nbytes;
		/*Leer archivo por archivo*/

		while (  (nbytes = read(fd[i],buffer,BUFSIZ)) > 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
tamao especfico. Desde la linea de comando debe recibir como primer
argumento el tamao 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 <unistd.h>

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 <unistd.h>

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 <unistd.h>
#include <sys/fcntl.h>

#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 posicin.

UNIX ofrece la llamada lseek, que tiene el siguiente formato:

#include <sys/types.h>
#include <unistd.h>

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 posicin 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 posicin 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 <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>

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<nbytes;i++) {
		printf("En posicion %d esta el caracter %c\n",offset+i,buffer[i]);
	}


	
	/*Posicionarse offset bytes, con respecto a la posicion actual*/
	printf("Offset relativo a la posicion actual\n");
	
	offset=	lseek(fd,offset,SEEK_CUR);

	if (offset < 0) {
		perror("Error en lseek SEEK_CUR");
		exit(3);
	}
	
	nbytes=	read(fd,buffer,20);

	for (i=0;i<nbytes;i++) {
		printf("En posicion %d esta el caracter %c\n",offset+i,buffer[i]);
	}


        /*Posicionarse offset bytes, con respecto al fin de archivo*/
	printf("Offset relativo al fin de archivo\n");

        offset= lseek(fd,-offset,SEEK_END);

        if (offset < 0) {
                perror("Error en lseek SEEK_EMD");
                exit(3);
        }

        nbytes= read(fd,buffer,20);

        for (i=0;i<nbytes;i++) {
                printf("En posicion %d esta el caracter %c\n",offset+i,buffer[i]
);
        }

	
}

Ejemplo12 10 Ejemplo12.c

Offset relativo al inicio
En posicion 10 esta el caracter 2
En posicion 11 esta el caracter  
En posicion 12 esta el caracter U
En posicion 13 esta el caracter s
En posicion 14 esta el caracter o
En posicion 15 esta el caracter  
En posicion 16 esta el caracter d
En posicion 17 esta el caracter e
En posicion 18 esta el caracter  
En posicion 19 esta el caracter o
En posicion 20 esta el caracter f
En posicion 21 esta el caracter f
En posicion 22 esta el caracter s
En posicion 23 esta el caracter e
En posicion 24 esta el caracter t
En posicion 25 esta el caracter  
Offset relativo a la posicion actual
En posicion 36 esta el caracter d
En posicion 37 esta el caracter e
En posicion 38 esta el caracter  
En posicion 39 esta el caracter <
En posicion 40 esta el caracter s
En posicion 41 esta el caracter y
En posicion 42 esta el caracter s
En posicion 43 esta el caracter /
En posicion 44 esta el caracter t
En posicion 45 esta el caracter y
En posicion 46 esta el caracter p
En posicion 47 esta el caracter e
En posicion 48 esta el caracter s
En posicion 49 esta el caracter .
En posicion 50 esta el caracter h
En posicion 51 esta el caracter >
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 tamao de un archivo

/*Ejemplo13 Calcula tamao de un archivo*/
#include <unistd.h>

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 <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>

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,&registro,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 <unistd.h>
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 <unistd.h> 
#include <sys/fcntl.h> 

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 <unistd.h> 
#include <sys/fcntl.h> 

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 travs de un directorio.

Un directorio queda definido por su ruta. Sin embargo, un directorio
contiene como informacin, 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 <sys/types.h>
	#include <sys/stat.h>
	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 <sys/types.h>
#include <sys/stat.h>

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 <unistd.h>
	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 <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 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 Mar 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 <unistd.h>

	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 <unistd.h>
/*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 podra utilizar la llamada read ?

	El siguiente ejemplo lo muestra:

/* Ejemplo22 */
#include <fcntl.h>

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 <sys/types.h>
#include <dirent.h>

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 <sys/types.h>
#include <dirent.h>


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, tamao, propietarios.

La llamada stat y fstat permite realizar esto, su formato es:

#include <sys/types.h>
#include <sys/stat.h>

int stat(const char* path, struct stat * buf)
int fstat(int fd, struct stat * buf)

En una funcin 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 <sys/types.h>
#include <sys/stat.h>

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, tamao,
tiempo de ultima modificacin 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 ao
tendra 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
orgen.

+ 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 simblica.
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 estndar, que normalmente es el teclado, puede
modificarse y hacer que apunte a un archivo diferente. O la salida estndar,
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 <unistd.h>
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 <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 estndar 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 estndar 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 <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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 <sys/types.h>
     #include <unistd.h>
     #include <fcntl.h>

     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 parmetros 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 <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

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 diseo 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 <stropts.h>
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 <unistd.h>
#include <stropts.h>

int ioctl(int fildes, int request, /*arg*/ ...)

Recibe el descriptor de archivos, un numero que indica una peticin hacia
el dispositivo y que VARIA por cada manejador de dispositivo y un conjunto
de argumentos, que varan en tipo y nmero dependiendo de la peticin.

Es posible identificar los mdulos de procesamiento de STREAMS de un
manejador de dispositivos, utilizando la llamada ioctl. El siguiente
ejemplo lo muestra.

/*Muestra el uso de STREAMS*/

#include <sys/types.h>
#include <stropts.h>
#include <sys/conf.h>
#include <fcntl.h>

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<n_mods;i++) 
		printf("%s\n",modulos.sl_modlist[i].l_name);

	close(fd);
 
}
main(int argc,char * argv[]) {

	int fd;
	if (argc==1) {
		printf("Ejemplo29: Ejemplo29 archivo");
		exit(1);
	}    
	fd=open(argv[1],O_RDONLY);
	if (fd < 0) {
		perror("No puedo abrir el archivo");
		exit(2);
	}

	informacionStreams(fd);
	informacionStreams(0);
	informacionStreams(1);
	exit(0);
}


Aplicar

Ejemplo29 /dev/console
es una archivo de STREAMS
Numero Modulos 5
redirmod
ttcompat
ldterm
ptem
pts
es una archivo de STREAMS
Numero Modulos 4
ttcompat
ldterm
ptem
pts
es una archivo de STREAMS
Numero Modulos 4
ttcompat
ldterm
ptem
pts

Cada modulo listado, se encarga de una tarea especial, por ejemplo,
controlar el caracter de borrado, o la velocidad de la terminal o
controlar la posicion en pantalla.

Este ejemplo se puede aplicar sobre el device driver /dev/tcp

Ejemplo29 /dev/tcp
es una archivo de STREAMS
Numero Modulos 2
tcp
ip
es una archivo de STREAMS
Numero Modulos 4
ttcompat
ldterm
ptem
pts
es una archivo de STREAMS
Numero Modulos 4
ttcompat
ldterm
ptem
pts

Esto implica, que al enviar mensajes TCP, el modulo procesador tcp los procesa
y delega lo demas al modulo ip. Al final el modulo ip acaba por entregar
el mensaje al device driver de la tarjeta de red.

Por tanto, con la llamada ioctl y teniendo la documentacion de un STREAM,
es posible controlar un dispositivo.

El siguiente ejemplo manipula el STREAM ttcompat (probado en Solaris) 
y cambia los caracters de borrado y eliminacion de un comando a z y w

#include <fcntl.h>
#include <sys/ttold.h>

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 patrn dentro de un conjunto de directorios.

/*Ejemplo31*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

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 magnticas ?
	
7. Comparar manejo de archivos de NT y UNIX.


8. Leer el capitulo 3 y 5 del libro "Sistemas Operativos. Diseo e Implantacin"
de Tanenbaum y explicar:
	+ Manejo de Hardware E/S.
	+ Software de E/S.
	+ Manejadores de dispositivos en Minix
	+ Administracin de discos y terminales.
	+ Sistema de archivos en MINIX.

9. Investigar el uso del floppy en el sistema UNIX donde se realice 
la prctica.
