ERROR USUARIO NO VALIDADO 1
El módulo de ficheros se estructura en los siguientes apartados:
NOTA: No realizar estos ejercicios en carpetas compartidas con otro sistema operativo, pues los permisos de los ficheros pueden cambiar respecto de los indicados aquí. Esto ocurre por ejemplo en Linux virtualizado sobre Windows, si se han configurado carpetas compartidas. Basta con trabajar sobre carpetas de Linux que no estén compartidas.
Este módulo incide fundamentalmente en los servicios sobre ficheros y directorios suministrados por un sistema operativo tipo UNIX.
Los programas utilizados en el módulo son los siguientes:
Los servicios UNIX utilizados en dichos programas son los siguientes:
Las funciones de biblioteca utilizadas en dichos programas son las siguientes:
Los mandatos utilizados en este módulo son:
La función principal de los directorios es presentar una visión lógica simple al usuario del sistema de directorios. Un directorio es un fichero que contiene una tabla de elementos de directorio. Un directorio contendrá tantos elementos como ficheros sean accesibles a través de él. Cada elemento es de tamaño variable e incluye el nodo_i y el nombre de un fichero.
Como ya sabemos, existen mandatos externos, como ls que nos permiten conocer el contenido de un directorio y las características de los elementos que contiene. Además, el lenguaje C, nos ofrece una serie de funciones de la familia opendir, que nos van a permitir acceder al contenido de cualquier directorio y conocer su contenido.
Analicemos el código del programa Listar_Directorio.c.
LD-01.- #define MYNAME "Listar_Directorio" LD-02.- #include <sys/types.h> LD-03.- #include <sys/stat.h> LD-04.- #include <dirent.h> LD-05.- #include <errno.h> LD-06.- #include <stdio.h> LD-07.- #include <stdlib.h> LD-08.- #include <unistd.h> LD-09.- int main(int argc, char * argv[]) LD-10.- { LD-11.- DIR * dir; LD-12.- struct dirent *entrada; LD-13.- LD-14.- if (argc != 2) LD-15.- { LD-16.- printf("Uso: "MYNAME" directorio\n"); exit(0); LD-17.- } LD-18.- LD-19.- dir = opendir(argv[1]); LD-20.- if (dir == NULL) LD-21.- { LD-22.- perror(MYNAME": opendir()"); exit(1); LD-23.- } LD-24.- LD-25.- printf("%10s %s\n", "Nodo_i", "Nombre"); LD-26.- LD-27.- while ((entrada = readdir(dir)) != NULL) LD-28.- printf("%10ld %s\n", entrada->d_ino, entrada->d_name); LD-29.- LD-30.- if (!entrada && errno != 0) LD-31.- { LD-32.- perror(MYNAME": readdir()"); exit(1); LD-33.- } LD-34.- closedir(dir); LD-35.- return 0; LD-36.- }
A partir del código anterior, podemos deducir, que el programa Listar_Directorio recibe un argumento de entrada, que es el directorio que se quiere listar. Adicionalmente se utilizan dos estructuras de datos para manejar los directorios. Por un lado DIR, que es el tipo de datos que representa a un directorio, y por otra parte, la estructura dirent. Este último tipo es el que se utiliza para representar cada uno de los elementos del directorio. (Observe que es el tipo del valor devuelto por la función readdir).
La definición de la estructura dirent incluye como mínimo los siguientes elementos:
struct dirent { long d_ino; // Nodo_i char d_name[]; // Nombre del fichero };
Sin embargo, dicha definición depende de la plataforma concreta y podría incluir otros elementos. Por ejemplo:
struct dirent { u64 d_ino; // Nodo_i s64 d_off; // Posición en el fichero del elemento del directorio unsigned short d_reclen; // Tamaño del directorio unsigned char d_type; // Tipo del elemento char d_name[0]; // Nombre del fichero };
Compile el programa Listar_Directorio.c.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Listar_Directorio.c -o Listar_Directorio
Cada proceso posee una serie de atributos asociados mantenidos en su BCP, uno de ellos es el identificador del nodo_i de su directorio actual de trabajo. El servicio chdir permite cambiar ese atributo para el proceso que lo solicita. La función de biblioteca getcwd permite averiguar la ruta al directorio actual de trabajo.
Analice el código del programa CambiaDir.c
CD-01.- #define MYNAME "CambiaDir" CD-02.- #include <limits.h> CD-03.- #include <stdio.h> CD-04.- #include <unistd.h> CD-05.- int main(int argc, char * argv[]) CD-06.- { CD-07.- char path[PATH_MAX+1]; CD-08.- if (argc != 2) CD-09.- printf("Uso: %s directorio\n", MYNAME); CD-10.- else CD-11.- { CD-12.- printf("%s: Antes cwd=%s\n", MYNAME, getcwd(path, PATH_MAX)); CD-13.- if (chdir(argv[1]) < 0) CD-14.- { CD-15.- fprintf(stderr, "%s: ", MYNAME); perror(argv[1]); CD-16.- } CD-17.- printf("%s: Despues cwd=%s\n", MYNAME, getcwd(path, PATH_MAX)); CD-18.- } CD-19.- return 0; CD-20.- }
La ejecución de dicho programa cambia el directorio actual de trabajo del proceso CambiaDir. Es importante recordar que estos atributos son propios de los procesos, por lo que aunque un proceso cambie su directorio actual de trabajo, este cambio no afectará, por ejemplo, al proceso Shell desde el que se invocó el programa. Esto lo podemos comprobar en la siguiente secuencia de mandatos:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ pwd /home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc CambiaDir.c -o CambiaDir alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./CambiaDir / CambiaDir: Antes cwd=/home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros CambiaDir: Despues cwd=/
Terminado el proceso CambiaDir se vuelve al Shell, que conserva su directorio de trabajo.
/home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros$ pwd /home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros
Si se quiere cambiar el directorio actual de trabajo del Shell deberá ser el propio proceso Shell el que solicite el servicio chdir, no puede delegar esa tarea a un proceso hijo. Esta es la razón por la cual el mandato cd ha de ser interno y no externo. Este mandato interno cd además mantendrá actualizada la variable de entorno PWD con el valor de la ruta al directorio actual de trabajo del propio Shell.
El servicio open permite abrir un fichero. Para ello se debe especificar el nombre del fichero (con su ruta de acceso) y una serie de opciones que especifica el modo de apertura del fichero. Las opciones de apertura son las siguientes: (Recordatorio: En lenguaje C, las constantes que comienzan por 0 están en octal.)
O_ACCMODE 0003 O_RDONLY 00 O_WRONLY 01 O_RDWR 02 O_CREAT 0100 O_EXCL 0200 O_NOCTTY 0400 O_TRUNC 01000 O_APPEND 02000 O_NONBLOCK 04000 O_SYNC 010000 O_ASYNC 020000
Analice el programa Abrir.c
A-01.- #define MYNAME "Abrir" A-02.- #include <sys/types.h> A-03.- #include <sys/stat.h> A-04.- #include <pwd.h> A-05.- #include <grp.h> A-06.- #include <fcntl.h> A-07.- #include <stdio.h> A-08.- #include <stdlib.h> A-09.- #include <time.h> A-10.- #include <unistd.h> A-11.- A-12.- /* Se omite la función mode2str */ A-13.- A-14.- int main(int argc, char * argv[]) A-15.- { A-16.- char * ptr = 0; A-17.- int perm = 0777; A-18.- int mode = 0; A-19.- char * file = 0; A-20.- int fd, ret; A-21.- struct stat stt[1]; A-22.- switch (argc) A-23.- { A-24.- case 4: /* perm */ A-25.- perm = strtol(argv[3], &ptr, 8); A-26.- if (!ptr || *ptr) break; A-27.- case 3: /* mode */ A-28.- mode = strtol(argv[2], &ptr, 8); A-29.- if (!ptr || *ptr) break; A-30.- case 2: /* file */ A-31.- file = argv[1]; A-32.- } A-33.- A-34.- if (!file) A-35.- { A-36.- printf("Uso: "MYNAME" archivo [modo [permisos]]\n"); A-37.- /* Más líneas sobre uso que se omiten */ A-38.- } A-39.- else A-40.- { A-41.- printf(MYNAME": open(\"%s\",0%o,0%o);\n", file, mode, perm); A-42.- fd = open(file, mode, perm); A-43.- if (fd < 0) A-44.- { A-45.- perror(MYNAME": open"); A-46.- ret = stat(argv[1], stt); A-47.- } A-48.- else A-49.- ret = fstat(fd, stt); A-50.- A-51.- if (ret < 0) A-52.- perror(MYNAME": f/stat()"); A-53.- else A-54.- { A-55.- printf(MYNAME": dev = %d\n", (int)stt->st_dev); A-56.- printf(MYNAME": ino = %ld\n", stt->st_ino); A-57.- printf(MYNAME": mode = %0o (%s)\n", stt->st_mode, mode2str(stt->st_mode)); A-58.- printf(MYNAME": nlnks = %d\n", stt->st_nlink); A-59.- printf(MYNAME": uid = %d (%s)\n", stt->st_uid, getpwuid(stt->st_uid)->pw_name); A-60.- printf(MYNAME": gid = %d (%s)\n", stt->st_gid, getgrgid(stt->st_gid)->gr_name); A-61.- printf(MYNAME": rdev = %d\n", (int)stt->st_rdev); A-62.- printf(MYNAME": size = %ld\n", stt->st_size); A-63.- printf(MYNAME": blksz = %ld\n", stt->st_blksize); A-64.- printf(MYNAME": nblks = %d\n", (int)stt->st_blocks); A-65.- printf(MYNAME": atime = %s", ctime(&stt->st_atime)); A-66.- printf(MYNAME": mtime = %s", ctime(&stt->st_mtime)); A-67.- printf(MYNAME": ctime = %s", ctime(&stt->st_ctime)); A-68.- } A-69.- } A-70.- return 0; A-71.- }
El programa anterior admite tres argumentos. El primero es obligatorio y especifica el nombre del fichero a abrir. El segundo es opcional y especifica el modo en que se abre el fichero. El tercero, también opcional, sirve para indicar los permisos del fichero, sólo se utiliza si se está creando un fichero.
Compile el programa Abrir.c.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Abrir.c -o Abrir
Si ejecutamos el programa anterior sobre un fichero inexistente, se produce el siguiente error:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Abrir nuevo Abrir: open("nuevo",00,0777); Abrir: open: No such file or directory Abrir: f/stat(): No such file or directory
Ejecutemos el programa Abrir sobre un fichero inexistente pero con la opción de O_CREAT y permisos = 0666, crearemos un fichero con tamaño real 0.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Abrir nuevo 000100 0666 Abrir: open("nuevo",0100,0666); Abrir: dev = 64773 Abrir: ino = 230599 Abrir: mode = 100644 (-rw-r--r--) <<-- Permisos Abrir: nlnks = 1 Abrir: uid = 27182 (alumno) Abrir: gid = 6000 (GrupoAlumno) Abrir: rdev = 0 Abrir: size = 0 Abrir: blksz = 4096 Abrir: nblks = 0 Abrir: atime = Wed Nov 23 12:33:19 2005 Abrir: mtime = Wed Nov 23 12:33:19 2005 Abrir: ctime = Wed Nov 23 12:33:19 2005
Al fichero creado, podemos cambiarle los permisos.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ chmod 0600 nuevo
Incluso podremos escribir en el fichero.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ echo HOLA >> nuevo
Utilizando el mandato cat podemos acceder al contenido del fichero. Podemos comprobar como el fichero tiene el contenido escrito mediante el mandato echo y la redirección al fichero.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ cat nuevo HOLA
Ejecutemos el programa Abrir sobre un fichero existente con la opción de O_WRONLY.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Abrir nuevo 01 Abrir: open("nuevo",01,0777); Abrir: dev = 64773 Abrir: ino = 230599 Abrir: mode = 100600 (-rw-------) <<-- El chmod cambió los permisos Abrir: nlnks = 1 Abrir: uid = 27182 (alumno) Abrir: gid = 6000 (GrupoAlumno) Abrir: rdev = 0 Abrir: size = 5 <<-- Tamaño alterado por el echo Abrir: blksz = 4096 Abrir: nblks = 8 Abrir: atime = Wed Nov 23 12:34:28 2005 Abrir: mtime = Wed Nov 23 12:34:22 2005 Abrir: ctime = Wed Nov 23 12:34:22 2005
Ejecutemos el programa Abrir sobre un fichero existente con las opciones de O_CREAT | O_RDWR y permisos = 0666.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Abrir nuevo 000102 0666 Abrir: open("nuevo",0102,0666); Abrir: dev = 64773 Abrir: ino = 230599 Abrir: mode = 100600 (-rw-------) <<-- No cambia Abrir: nlnks = 1 Abrir: uid = 27182 (alumno) Abrir: gid = 6000 (GrupoAlumno) Abrir: rdev = 0 Abrir: size = 5 <<-- No cambia Abrir: blksz = 4096 Abrir: nblks = 8 Abrir: atime = Wed Nov 23 12:34:28 2005 Abrir: mtime = Wed Nov 23 12:34:22 2005 Abrir: ctime = Wed Nov 23 12:34:22 2005
Observe que no cambian los permisos ni el tamaño del fichero. Pista: Consultar en las transparencias de teoría de este capítulo, o bien en man 2 open, cómo actúa O_CREAT cuando el fichero ya existe, en particular qué ocurre con los permisos solicitados.
Ejecutemos el programa Abrir sobre un fichero existente con la opción de O_TRUNC | O_WRONLY.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Abrir nuevo 001001 Abrir: open("nuevo",01001,0777); Abrir: dev = 64773 Abrir: ino = 230599 Abrir: mode = 100600 (-rw-------) Abrir: nlnks = 1 Abrir: uid = 27182 (alumno) Abrir: gid = 6000 (GrupoAlumno) Abrir: rdev = 0 Abrir: size = 0 <<-- Se trunca Abrir: blksz = 4096 Abrir: nblks = 0 Abrir: atime = Wed Nov 23 12:34:28 2005 Abrir: mtime = Wed Nov 23 12:35:01 2005 Abrir: ctime = Wed Nov 23 12:35:01 2005
Observe que el fichero ha sido truncado por lo que pierde todo su contenido y queda en tamaño 0.
Borre el fichero nuevo.
Los servicios read y write nos permiten acceder al contenido de un fichero o bien modificarlo.
Los servicios read y write devuelven el número de bytes realmente leídos o escritos, que puede ser distinto del solicitado. Este comportamiento es fácil de comprobar con el read, pero es más difícil con el write, ya que en ficheros o pipes se suele escribir lo solicitado a menos que falten recursos (p. ej. que se llene el disco).
Para comprobar el funcionamiento de estos servicios, analizaremos el programa LeeyEscribe.c
LE-01.- #define MYNAME "LeeyEscribe" LE-02.- #include <sys/types.h> LE-03.- #include <sys/stat.h> LE-04.- #include <fcntl.h> LE-05.- #include <stdio.h> LE-06.- #include <unistd.h> LE-07.- int main (int argc, char * argv[]) LE-08.- { LE-09.- int perm = 0600; // Lectura y Escritura LE-10.- int mode = O_CREAT|O_TRUNC|O_RDWR; //1102; // O_CREATE|O_RDWR LE-11.- int fd,i; LE-12.- int cnt_Lect, cnt_Esc; LE-13.- char buff[512]; LE-14.- LE-15.- if (argc!=2) LE-16.- printf("Uso: "MYNAME" archivo \n"); LE-17.- else LE-18.- { LE-19.- fd = open(argv[1],mode,perm); LE-20.- if (fd<0) LE-21.- perror(MYNAME": open"); LE-22.- else LE-23.- { LE-24.- while ((cnt_Lect = read(0, buff, 512)) > 0) LE-25.- { LE-26.- printf("LEIDOS: %d - ", cnt_Lect); LE-27.- for (i = 0; i < cnt_Lect; i++) LE-28.- { LE-29.- if (iscntrl(buff[i])) LE-30.- printf("%03o,", (int)buff[i]); LE-31.- else LE-32.- printf("'%c',", buff[i]); LE-33.- } LE-34.- printf("\n"); LE-35.- cnt_Esc=write(fd, buff, cnt_Lect); LE-36.- if (cnt_Esc<0) LE-37.- perror(MYNAME); LE-38.- } LE-39.- close(fd); LE-40.- } LE-41.- } LE-42.- return 0; LE-43.- }
Compile el programa LeeyEscribe.c.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc LeeyEscribe.c -o LeeyEscribe
A continuación ejecute el programa e introduzca por el teclado varias líneas de longitud diferente. Tenga en cuenta que:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./LeeyEscribe fichero.txt abc[Enter] LEIDOS: 4 - 'a','b','c',012, [Enter] <--- Sólo el [Enter] LEIDOS: 1 - 012, xyz[Ctrl+D] LEIDOS: 3 - 'x','y','z', mno[Enter] LEIDOS: 4 - 'm','n','o',012, [Ctrl+D] <--- Sólo un [Ctrl+D]
Observe que a pesar de que en la llamada a read se especifica que se leerán 512 bytes, la cantidad de bytes realmente depende de la información disponible.
Nótese que el [Ctrl+D] no se envía a read(), por lo que no lo contabiliza. Aunque [Ctrl+D] se asocia a 'fin de fichero', realmente es un carácter de control para liberar el buffer de E/S del terminal, pero no se transmite un byte EOF como tal. Además, no impide que se sigan almacenando caracteres tecleados en el buffer. Cuando se teclea un [Ctrl+D] aislado, read() recibe 0 bytes por lo que retorna 0 al programa.
Un mismo fichero puede tener más de un nombre. Estos alias se crean con los servicios de link y symlink.
La diferencia fundamental entre los enlaces físicos y los enlaces simbólicos es que los primeros son referencias directas, mientras que los segundos son una referencia que se establece a través de un nombre. Para comprobar el funcionamiento de los enlaces, tanto físicos como simbólicos, utilizaremos un conjunto de programas: CreaDir.c, EnlaceF.c, EnlaceS.c. Dichos programas se limitan a utilizar, respectivamente, los servicios mkdir, link, symlink.
CD-01.- #define MYNAME "CreaDir" CD-02.- #include <sys/stat.h> CD-03.- #include <sys/types.h> CD-04.- #include <stdio.h> CD-05.- #include <stdlib.h> CD-06.- int main(int argc, char * argv[]) CD-07.- { CD-08.- char * ptr = NULL; CD-09.- int perm = 0777; CD-10.- CD-11.- if (argc == 3) CD-12.- { CD-13.- perm = strtol(argv[2], &ptr, 8); CD-14.- if (!ptr || *ptr) return 1; CD-15.- mkdir(argv[1], perm); CD-16.- } CD-17.- else if (argc == 2) CD-18.- mkdir(argv[1], 0777); CD-19.- else CD-20.- printf("USO: %s nombre [permisos]\n", MYNAME); CD-21.- CD-22.- return 0; CD-23.- }
Analizando el código anterior, podemos ver que el objetivo de programa es crear un directorio (especificado en el primer parámetro), con los permisos indicados en el segundo parámetro.
A continuación vamos a compilar el programa y vamos a crear un directorio Pruebas, en nuestro directorio home.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc CreaDir.c -o CreaDir alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./CreaDir ~/Pruebas 0700 alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ls -l ~ total 4 drwxr-xr-x 2 alumno GrupoAlumno 536 Jan 23 12:02 PracticasAnalisis drwx------ 2 alumno GrupoAlumno 48 Jan 23 12:02 Pruebas
Para comprobar el funcionamiento de los enlaces físicos y simbólicos vamos a proceder, en primer lugar, a crear un fichero. Para ello, en nuestro directorio local, podemos crear un fichero redirigiendo la salida del mandato ls -l al fichero Pru.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros $ ls -l ~ > Pru
Mediante el mandato stat, podemos comprobar el estado del fichero recién creado.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ stat Pru File: 'Pru' Size: 226 Blocks: 8 IO Block: 131072 regular file Device: fd05h/64773d Inode: 343141 Links: 1 Access: (0644/-rw-r--r--) Uid: (27182/ alumno) Gid: ( 6000/ GrupoAlumno) Access: 2006-01-23 12:03:35.000000000 +0100 Modify: 2006-01-23 12:03:35.000000000 +0100 Change: 2006-01-23 12:03:35.000000000 +0100
El siguiente paso es crear un enlace físico a dicho fichero. Para ello vamos a utilizar el programa EnlaceF.c.
EF-01.- #define MYNAME "EnlaceF" EF-02.- #include <stdio.h> EF-03.- #include <stdlib.h> EF-04.- #include <unistd.h> EF-05.- EF-06.- int main(int argc, char * argv[]) EF-07.- { EF-08.- if (argc == 3) EF-09.- link(argv[1], argv[2]); EF-10.- else EF-11.- printf("USO: %s fich_existente nuevo_nombre\n", MYNAME); EF-12.- EF-13.- return 0; EF-14.- }
Tras compilar el programa anterior, vamos a utilizarlo para crear un enlace al fichero Pru anteriormente creado.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc EnlaceF.c -o EnlaceF alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./EnlaceF Pru ~/Pruebas/Pru2
A continuación vamos a crear un enlace simbólico mediante el programa EnlaceS.c.
ES-01.- #define MYNAME "EnlaceS" ES-02.- #include <stdio.h> ES-03.- #include <stdlib.h> ES-04.- #include <unistd.h> ES-05.- int main(int argc, char * argv[]) ES-06.- { ES-07.- if (argc == 3) ES-08.- symlink(argv[1], argv[2]); ES-09.- else ES-10.- printf("USO: %s fich_existente nuevo_nombre\n", MYNAME); ES-11.- ES-12.- return 0; ES-13.- }
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc EnlaceS.c -o EnlaceS alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./EnlaceS ~/PracticasAnalisis/ModuloFicheros/Pru ~/Pruebas/Pru3
Por su lado, Pru3 tiene las siguientes características.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros $ stat ~/Pruebas/Pru3 File: '~/Pruebas/Pru3' -> '/home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros/Pru' Size: 40 Blocks: 0 IO Block: 131072 symbolic link Device: fd05h/64773d Inode: 557777 Links: 1 Access: (0777/lrwxrwxrwx) Uid: (27182/ alumno) Gid: ( 6000/ GrupoAlumno) Access: 2006-01-23 12:04:46.000000000 +0100 Modify: 2006-01-23 12:04:46.000000000 +0100 Change: 2006-01-23 12:04:46.000000000 +0100
Hacemos un ls del directorio en el que hemos creado los enlaces.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros $ ls -l ~/Pruebas/ total 4 -rw-r--r-- 2 alumno GrupoAlumno 226 Jan 23 12:03 Pru2 lrwxrwxrwx 1 alumno GrupoAlumno 40 Jan 23 12:04 Pru3-> /home/alumnosSSOO/alumno/PracticasAnalisis/ModuloFicheros/Pru
Observe que el enlace simbólico indica el nombre completo del fichero sobre el que ha hecho el enlace.
Borre los todos los ficheros Pru y el directorio Pruebas.
En el módulo de arquitectura e introducción al S.O., ya vimos que cada proceso dispone de tres descriptores estándar (entrada, salida y error) y también vimos como redirigir dichos descriptores desde el Shell.
En el caso de querer redirigir dichos descriptores desde en un programa, basta con cerrar el descriptor mediante el servicio close y seguidamente crear un nuevo descriptor, que se asignará al hueco dejado por el close anterior. Esta última operación (la de crear el nuevo descriptor) es conveniente realizarla mediante el servicio dup, puesto que si se utiliza el servicio open y éste falla, nos encontraremos sin el correspondiente descriptor estándar.
Para comprobar el funcionamiento de las redirecciones de descriptores estándar, utilizaremos el programa Redirige.c
RD-01.- #define MYNAME "Redirige" RD-02.- #include <fcntl.h> RD-03.- #include <stdio.h> RD-04.- #include <stdlib.h> RD-05.- #include <unistd.h> RD-06.- int main(int argc, char * argv[]) RD-07.- { RD-08.- int fd1, fd2; RD-09.- if (argc > 3) RD-10.- { RD-11.- fd1 = open(argv[1], O_RDONLY); RD-12.- if (fd1 < 0) RD-13.- { RD-14.- perror(MYNAME": 1"); return 1; RD-15.- } RD-16- RD-17.- fd2 = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0600); RD-18.- if (fd2 < 0) RD-19.- { RD-20.- close(fd1); RD-21.- perror(MYNAME": 2"); return 2; RD-22.- } RD-23.- close(0); RD-24.- dup(fd1); RD-25.- close(fd1); RD-26.- close(1); RD-27.- dup(fd2); RD-28.- close(fd2); RD-29.- execvp(argv[3], &argv[3]); RD-30.- perror(MYNAME": 3"); return 3; RD-31.- } RD-32.- else RD-33.- printf("USO: %s fichero_entrada fichero_salida programa [argumentos]\n", MYNAME); RD-34.- RD-35.- return 0; RD-36.- }
Observe que el primer argumento se toma como fichero para redirigir la entrada estándar, el segundo argumento se toma como fichero para redirigir la salida estándar y el resto de los argumentos corresponde al programa que se ejecutará con el servicio exec.
Las tuberías permiten comunicar el proceso que creó la tubería con procesos descendientes.
En el módulo de arquitectura e introducción al S.O. ya vimos como la utilización de tuberías permitía comunicar dos procesos del mismo computador. En concreto, veíamos como el carácter «|» se utiliza en el Shell para unir dos mandatos mediante una tubería de modo que la salida generada por el primer mandato actúe como entrada del segundo mandato (básicamente, en este caso se trata de una redirección de los descriptores estándar).
Para ver el procedimiento de creación de tuberías para la comunicación entre procesos, analizaremos el programa Tuberia.c
T-01.- #define MYNAME "Tuberia" T-02.- #include <stdio.h> T-03.- #include <stdlib.h> T-04.- #include <unistd.h> T-05.- #define MAX_BUF 10 T-06.- int main(void) T-07.- { T-08.- int fd[2], nleidos; T-09.- char buffer[MAX_BUF]; T-10.- T-11.- if (pipe(fd) < 0) T-12.- { T-13.- perror(MYNAME": Error al crear el pipe\n"); T-14.- return 1; T-15.- } T-16.- T-17.- switch(fork()) T-18.- { T-19.- case -1: T-20.- perror(MYNAME": fork()"); T-21.- return 2; T-22.- case 0: /*LECTOR DEL PIPE (Proceso HIJO) */ T-23.- close (fd[1]); T-24.- do T-25.- { T-26.- nleidos = read(fd[0], buffer, MAX_BUF); T-27.- if (nleidos < 0) T-28.- perror(MYNAME" - PROCESO Y: Error en la lectura\n"); T-29.- else T-30.- { T-31.- fprintf(stderr, MYNAME" - PROCESO Y: %d datos: ", nleidos); T-32.- write (1, buffer, nleidos); T-33.- fprintf(stderr, "\n"); T-34.- } T-35.- } while (nleidos > 0); T-36.- fprintf(stderr, MYNAME" - PROCESO Y: termina correctamente\n"); T-37.- break; T-38.- default: /*ESCRITOR DEL PIPE (Proceso PADRE) */ T-39.- close (fd[0]); T-40.- do T-41.- { T-42.- nleidos = read(0, buffer, MAX_BUF); T-43.- fprintf(stderr, MYNAME" - PROCESO X: %d datos \n", nleidos); T-44.- write (fd[1], buffer, nleidos); T-45.- } while (nleidos > 0); T-46.- fprintf(stderr, MYNAME" - PROCESO X: termina correctamente\n"); T-47.- } T-48.- return 0; }
En la línea T-11, del código anterior, se procede a crear un tubería. Dicha tubería dispone de dos descriptores asociados, uno de escritura (fd[1]) y otro de lectura (fd[0]). A continuación se crea un proceso hijo. Ambos procesos conocen la existencia de la tubería (tanto el descriptor de lectura como el de escritura), por lo que podemos utilizar este mecanismo para comunicar ambos procesos.
Como podemos observar en la figura anterior, uno de los procesos (proceso escritor) depositará información en la tubería a través del descriptor de escritura, y el otro (proceso lector) recuperará la información de la tubería a través del descriptor de lectura. Otro aspecto a tener en cuenta, es que el proceso escritor, cierra el descriptor de lectura de la tubería (puesto que no lo va a utilizar), mientras que el proceso lector, cerrará el descriptor de escritura de la tubería.
Cuando se utilizan tuberías, es necesario tener en cuenta dos aspectos fundamentales:
Para este conjunto de ejercicios, además del programa Tubería.c, utilizaremos dos Shell.
Shell 1 Compile y ejecute el programaTubería. Teclee datos terminados en [Enter]. Cuando desee finalizar teclee [Ctrl+D].
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Tuberia.c -o Tuberia alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Tuberia 12345[Enter] Tuberia PROCESO X: 6 datos Tuberia PROCESO Y: 6 datos: 12345 1234567890123456789012345[Enter] Tuberia PROCESO X: 10 datos Tuberia PROCESO X: 10 datos Tuberia PROCESO X: 6 datos Tuberia PROCESO Y: 10 datos: 1234567890 Tuberia PROCESO Y: 10 datos: 1234567890 Tuberia PROCESO Y: 6 datos: 12345 [Ctr+D] Tuberia PROCESO X: 0 datos Tuberia PROCESO X: termina correctamente Tuberia PROCESO Y: 0 datos: Tuberia PROCESO Y: termina correctamente
Shell 1: Ejecute otra vez el programa.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Tuberia 123[Enter] Tuberia PROCESO X: 4 datos Tuberia PROCESO Y: 4 datos: 123
Shell 2: Teniendo los procesos Tuberia activos, abra otro Shell y mate al proceso padre.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ps -lu alumno F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 5 S 27182 32132 32118 0 75 0 - 1901 - ? 00:00:00 sshd 0 S 27182 2707 32132 0 76 0 - 826 wait pts/9 00:00:00 bash 0 S 27182 4479 32132 0 75 0 - 826 wait pts/14 00:00:00 bash 0 S 27182 12638 2707 0 76 0 - 360 - pts/9 00:00:00 Tuberia 1 S 27182 12639 12638 0 76 0 - 360 pipe_w pts/9 00:00:00 Tuberia 0 R 27182 12640 4479 0 76 0 - 649 - pts/14 00:00:00 ps alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ kill 12638 (en su caso será otro valor de PID)
Shell 1: Observe lo que ocurre en el Shell 1.
Tuberia PROCESO Y: 0 datos Tuberia PROCESO Y: termina correctamente Terminated
Shell 1: Ejecute otra vez el programa
Shell 2: Teniendo los procesos Tuberia activos, liste el contenido del subdirectorio /proc/pid_proceso_padre/fd
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ps -lu alumno F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 5 S 27182 32132 32118 0 75 0 - 1901 - ? 00:00:00 sshd 0 S 27182 2707 32132 0 75 0 - 826 wait pts/9 00:00:00 bash 0 S 27182 4479 32132 0 75 0 - 826 wait pts/14 00:00:00 bash 0 S 27182 13354 2707 0 76 0 - 360 - pts/9 00:00:00 Tuberia 1 S 27182 13355 13354 0 76 0 - 360 pipe_w pts/9 00:00:00 Tuberia 0 R 27182 13557 4479 0 76 0 - 649 - pts/14 00:00:00 ps alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ls -l /proc/13354/fd (en su caso será otro valor de PID)
Shell 2: Teniendo los procesos Tuberia activos, mate al proceso hijo.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ kill 13355 (en su caso será otro valor de PID)
Shell 1 Teclee unos caracteres terminado en [Enter] y observe lo que ocurre.
qwer[Enter] Tuberia PROCESO X: 5 datos
Se entiende por coutilización el hecho de que varios procesos estén accediendo simultáneamente al mismo fichero. El resultado obtenido dependerá de si los procesos comparten o no comparten el puntero de posición del fichero.
Cuando existen varios procesos lectores simultáneos no está predefinido el orden en que se ejecutan las lecturas. Es decir, el planificador de procesos determina qué proceso entra en ejecución en cada momento, por lo que a priori, no podremos conocer qué proceso lector está realizando la lectura en cada instante.
Para verificar este funcionamiento, utilizaremos el programa Lectores.c
L-01.- #define MYNAME "Lectores" L-02.- #include <sys/wait.h> L-03.- #include <ctype.h> L-05.- #include <stdio.h> L-06.- #include <stdlib.h> L-07.- #include <unistd.h> L-08.- int main(void) L-09.- { L-10.- int pp[2]; L-11.- int i; L-12.- char ch; L-13.- L-14.- if (pipe(pp) < 0) L-15.- { L-16.- perror(MYNAME": pipe()"); exit(1); L-17.- } L-18.- L-19.- for (i = 1; i <= 9; i++) /*Crea 9 hijos del mismo padre*/ L-20.- { L-21.- switch (fork()) L-22.- { L-23.- case -1: L-24.- perror(MYNAME": fork()"); L-25.- exit(1); L-26.- case 0: /* HIJO #i: */ L-27.- close(pp[1]); L-28.- while (read(pp[0], &ch, 1) == 1) L-29.- { L-30.- if (isdigit(ch)) ch += i; L-31.- write(1, &ch, 1); L-32.- } L-33.- exit(0); L-34.- } L-35.- } L-36.- /* PADRE: */ L-37.- close(pp[0]); L-38.- i = 0; L-39.- while (i < 1000) /* 1000 = 20 * 50. Escribe 20 líneas de 50 caracteres cada una*/ L-40.- { L-41.- write(pp[1], "0", 1); L-44.- i++; L-45.- if (i%50 == 0) L-46.- write(pp[1], "\n", 1); /* Añade cambio de línea (cada 50 caracteres) */ L-47.- } L-48.- close(pp[1]); L-49.- L-50.- for (i = 1; i <= 9; i++) L-51.- wait(NULL); L-52.- L-53.- return 0; L-54.- }
Compile y ejecute el programa Lectores varias veces y observe los resultados obtenidos.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Lectores.c -o Lectores alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Lectores
La función de biblioteca lseek, nos permite reposicionar el puntero de lectura/escritura de un fichero.
Para comprobar el funcionamiento de esta función de biblioteca, utilizaremos el programa Robot_B.c
RB-01.- #define MYNAME "Robot_B" RB-02.- #include <sys/types.h> RB-03.- #include <fcntl.h> RB-04.- #include <stdio.h> RB-05.- #include <stdlib.h> RB-06.- #include <unistd.h> RB-07.- int main(int argc, char * argv[]) RB-08.- { RB-09.- char ch; RB-10.- off_t offset = 0; RB-11.- int whence = -1; RB-12.- char * ptr = 0; RB-13.- int perm = 0777; RB-14.- int mode = 0; RB-15.- char * file = 0; RB-16.- int fd; RB-17.- RB-18.- switch (argc) RB-19.- { RB-20.- case 3: /* mode */ RB-21.- mode = strtol(argv[2], &ptr, 8); RB-22.- if (!ptr || *ptr) break; RB-23.- case 2: /* file */ RB-24.- file = argv[1]; RB-25.- } RB-26.- RB-27.- fd = open(file, mode, perm); RB-28.- if (fd < 0) RB-29.- { RB-30.- perror(MYNAME": open"); return 1; RB-31.- } RB-32.- RB-33.- while (read(0, &ch, 1) > 0) RB-34.- { RB-35.- switch (ch) RB-36.- { RB-37.- case '0' ... '9': RB-38.- offset *= 10; RB-39.- offset += ch - '0'; RB-40.- break; RB-41.- case '-': RB-42.- if (offset > 0) offset = -offset; RB-43.- break; RB-44.- case '+': RB-45.- if (offset < 0) offset = -offset; RB-46.- break; RB-47.- case '<': RB-48.- if (whence < 0) whence = SEEK_SET; RB-49.- case '=': RB-50.- if (whence < 0) whence = SEEK_CUR; RB-51.- case '>': RB-52.- if (whence < 0) whence = SEEK_END; RB-53.- offset = lseek(fd, offset, whence); RB-54.- if (offset < 0) RB-55.- perror(MYNAME": lseek()"); RB-56.- offset = 0; RB-57.- whence = -1; RB-58.- break; RB-59.- case 'a' ... 'z': RB-60.- case 'A' ... 'Z': RB-61.- if (write(fd, &ch, 1) < 0) RB-62.- perror(MYNAME" write()"); RB-63.- break; RB-64.- case '@': RB-65.- close(fd); RB-66.- read(0, &ch, 1); RB-67.- return 0; RB-68.- default: RB-69.- offset = 0; RB-70.- break; RB-71.- } RB-72.- } RB-73.- return 0; RB-74.- }
El programa recibe dos argumentos, de los cuales sólo uno es obligatorio. El primero es el nombre del fichero (obligatorio), y el segundo argumento (opcional) especifica las opciones que se utilizan en el servicio open. Una vez abierto el fichero, el programa entra en un bucle infinito de lectura de la entrada estándar y de escritura en el fichero abierto. Los caracteres leídos se interpretan de la siguiente forma:
Compruebe que no tiene un fichero fich en su directorio de trabajo, compile el programa Robot_B y realice la secuencia siguiente (observe que se van a utilizar 3 Shell):
Shell 1:Abrimos el fichero fich inexistente mediante el programa Robot_B.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Robot_B.c -o Robot_B alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fich Robot_B: open: No such file or directory
Podemos comprobar que si el fichero no existe y en el servicio open no se usan opciones de creación, se produce un error.
Shell 1:Abrimos el fichero con opciones de O_CREAT | O_RDWR, escribimos varias líneas y terminamos la ejecución del programa Robot_B con el carácter @.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fich 000102 abcdefghijk lmnopq @
Shell 3:Observamos el contenido del fichero fich. Para ello utilizamos el mandato od con la opción –a de alfanumérico. Dicho mandato imprime la dirección en octal seguida de los valores de 16 bytes.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 a b c d e f g h i j k l m n o p 0000020 q 0000021
Shell 1: Abrimos nuevamente el fichero con opciones de O_CREAT | O_RDWR y escribimos una línea.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fich 000102 xx
Shell 3: Observamos el contenido del fichero fich
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 x x c d e f g h i j k l m n o p 0000020 q 0000021
Vemos que se han sobrescrito los primeros caracteres del fichero, puesto que al abrir un fichero su puntero de posición apunta al inicio del mismo.
Shell 1: Nos ponemos en la posición 15 a contar desde el principio del fichero y escribimos una línea.
+15< xxxx
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 x x c d e f g h i j k l m n o x 0000020 x x x 0000023
Vemos que se han escrito los caracteres a partir de la posición 15. Se han sobrescrito el 15 y 16, y se han añadido el 17 y el 18.
Shell 2: Abrimos el fichero con opciones de O_CREAT | O_RDWR y escribimos una línea.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fich 000102 qqq
Shell 3: Observamos el contenido del fichero fich.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 q q q d e f g h i j k l m n o x 0000020 x x x 0000023
Vemos que se han sobrescrito los primeros caracteres del fichero.
Shell 1: Escribimos una línea y terminamos la ejecución del programa con @.
aaaaaaaaaaa @
Shell 3 : Observamos el contenido del fichero fich.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 q q q d e f g h i j k l m n o x 0000020 x x x x a a a a a a a a a a 0000036
Shell 2: Terminamos la ejecución del programa.
@
Shell 1: Abrimos nuevamente el fichero con las opciones O_APPEND | O_WRONLY. La opción O_APPEND hace que las escrituras siempre se añadan al final del fichero, con independencia del puntero de posición. Escribimos una línea.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fich 002001 ccc
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 q q q d e f g h i j k l m n o x 0000020 x a a a a a a a a a a a c c c
Vemos que la información se añade, no se sobrescribe al principio del fichero.
Shell 1: Nos ponemos al principio del fichero, escribimos una línea y terminamos el programa.
< hhh @
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fich 0000000 q q q d e f g h i j k l m n o x 0000020 x a a a a a a a a a a a c c c h 0000040 h h
Elimine el fichero fich.
Una vez invocado el servicio open sobre un fichero, el fichero continúa abierto, aunque se borre o se cambien los permisos de acceso.
Veamos en primer lugar como aunque cambiemos los permisos de un fichero abierto, este sigue abierto y el programa que abrió el fichero puede seguir trabajando con él. Para ello, realice la siguiente secuencia.
Shell 1: Creamos un fichero con el mandato cat y le introducimos contenido.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ cat > fichero abcdefghijk[Ctrl+D] <<-- Fin de fichero
Shell 2: Veamos los permisos del fichero creado.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ls -l fichero -rw-r--r-- 1 alumno GrupoAlumno 11 Dec 6 19:10 fichero
Shell 2 :Vemos el contenido del fichero fichero.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fichero 0000000 a b c d e f g h i j k
Shell 1: Abrimos el fichero con opciones de O_WRONLY, y escribimos una línea, terminando con un [Enter].
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fichero 000001 xxxx[Enter]
Shell 2: Observamos el contenido del fichero fichero
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fichero 0000000 x x x x e f g h i j k
Shell 2: Cambiamos los permisos del fichero fichero, de forma que no se pueda escribir.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ chmod 400 fichero
Shell 2: Comprobamos que se han cambiado los permisos del fichero fichero
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ls -l fi* -r-------- 1 Alumno GrupoAlumno 11 Dec 6 19:14 fichero
Shell 2 :Abrimos el fichero con opciones de O_WRONLY, y vemos que el fichero no se puede abrir, ya que es de solo lectura.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fichero 000001 Robot_B: open: Permission denied.
Shell 1: Escribimos otra línea (terminando con [Enter]) y terminamos la ejecución con el carácter @.
yyy[Enter] @
Observamos que no se produce ningún error y que el programa termina.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a fichero 0000000 x x x x y y y h i j k
Vemos que el fichero ha sido modificado, aunque sus permisos eran ya de solo lectura.
Borre el fichero fichero.
Comprobemos ahora como cuando un proceso tiene abierto un fichero, puede seguir trabajando con él a pesar de que el fichero se borre. Para ello, realice la siguiente secuencia.
Shell 1: Abrimos el fichero fichero con permisos O_CREAT | O_WRONLY mediante el programa Robot_B y escribimos una línea.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Robot_B fichero 0000101 aaaaaaa
Shell 2: Borramos el fichero creado.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ rm fichero
Shell 2: Comprobamos que el fichero fichero ya no existe.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ls -l fichero ls: fichero: No such file or directory
Shell 1: Escribimos un par de líneas (terminadas con [Enter]) y terminamos el programa Robot_B con el carácter @.
bbbbbb[Enter] ccc[Enter] @
Observamos que el programa ejecuta los servicios de write sin producir ningún tipo de error y termina correctamente.
Shell 2: Volvemos a comprobar que el fichero fichero ya no existe.
El servicio umask sirve para modificar la máscara de creación de ficheros del proceso que lo solicita. No confundir con el mandato de Shell umask, que permite ver y modificar la máscara del Shell (que será heredada por los procesos hijos).
Analice el programa Enmascarado.c.
E-01.- #define MYNAME "Enmascarado" E-02.- #include <sys/types.h> E-03.- #include <sys/stat.h> E-04.- #include <fcntl.h> E-05.- #include <stdio.h> E-06.- #include <stdlib.h> E-07.- #include <unistd.h> E-08.- int main(int argc, char * argv[]) E-09.- { E-10.- char * archivo = NULL; E-11.- int permisos = 0666, mascara = 0000; E-12.- int ret, ret1 = -1, ret2 = -1; E-13.- struct stat sts[1]; E-14.- int fd; E-15.- E-16.- if (argc > 1) E-17.- archivo = argv[1]; E-18.- if (argc > 2) E-19.- ret1 = sscanf(argv[2], "%o", &permisos); E-20.- if (argc > 3) E-21.- ret2 = sscanf(argv[3], "%o", &mascara); E-22.- if (!archivo || !ret1 || !ret2 || argc > 4) E-23.- { E-24.- printf("Uso: "MYNAME" fichero [permisos [mascara]]\n"); exit(0); E-25.- } E-26.- E-27.- if (ret2 == 1) E-28.- { E-29.- printf(MYNAME" Cambiando máscara a 0%03o\n", mascara); E-30.- mascara = umask(mascara); E-31.- if (mascara < 0) E-32.- { E-33.- perror(MYNAME": umask()"); exit(1); E-34.- } E-35.- printf(MYNAME" La máscara anterior era 0%03o\n", mascara); E-36.- } E-37.- else E-38.- { E-39.- mascara = umask(0); E-40.- umask(mascara); E-41.- printf(MYNAME" La máscara actual es 0%03o\n", mascara); E-42.- } E-43.- E-43.- ret = stat(archivo, sts); E-44.- if (ret < 0) E-45.- { E-46.- printf(MYNAME" Creando archivo \"%s\"\n", archivo); E-47.- printf(MYNAME" Aplicando permisos 0%03o\n", permisos); E-48.- } E-49.- else E-50.- { E-51.- printf(MYNAME" Truncando archivo existente \"%s\"\n", archivo); E-52.- printf(MYNAME" Permisos actuales del archivo 0%03o\n", sts->st_mode & ~S_IFMT); E-53.- } E-54.- E-55.- fd = creat(archivo, permisos); E-56.- if (fd < 0) E-57.- { E-58.- perror(MYNAME": creat()"); exit(1); E-59.- } E-60.- E-61.- ret = fstat(fd, sts); E-62.- E-63.- if (ret < 0) E-64.- { E-65.- perror(MYNAME": fstat()"); exit(1); E-66.- } E-67.- E-68.- printf(MYNAME" Tamaño final del archivo %d\n", (int) sts->st_size); E-69.- printf(MYNAME" Permisos finales del archivo 0%03o\n", sts->st_mode & ~S_IFMT); E-70.- E-71.- return 0; E-72.- }
Los permisos finales de un fichero se obtienen haciendo una operación AND lógico entre la mascara negada y los permisos especificados en la creación del fichero.
Supongamos que no existe el fichero F1.txt.
Compile y ejecute el programa con los siguientes argumentos:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc Enmascarado.c -o Enmascarado alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./Enmascarado F1.txt 0352 0127
Borre el fichero F1.txt
Cuando se escribe un fichero no es necesario hacerlo de forma secuencial. Se puede escribir en cualquier posición del mismo, incluso dejando sin escribir tramos intermedios. Un hueco (hole) es un tramo de bytes intermedios que no han sido escritos. Su lectura devuelve 0. Si una agrupación completa no ha sido escrita (forma parte de un hueco), habitualmente no se reserva soporte físico (disco) para ella. Simplemente se contabiliza como hueco. Esto da lugar a ficheros cuyo tamaño real es mayor que el espacio físico que ocupa. Para comprobar este efecto, utilizaremos el programa CreaHueco.c.
CH-01.- #define MYNAME "CreaHueco" CH-02.- #include <sys/types.h> CH-03.- #include <fcntl.h> CH-04.- #include <stdio.h> CH-05.- #include <unistd.h> CH-06.- int main(int argc, char * argv[]) CH-07.- { CH-08.- int flags; CH-09.- off_t offset; CH-10.- CH-11.- if (!argv[1] || sscanf(argv[1], "%lu", &offset) != 1) CH-12.- { CH-13.- printf("Uso: "MYNAME" offset > fichero_creado_nuevo\n"); CH-14.- printf("Uso: "MYNAME" offset >> fichero_para_alterar\n"); CH-15.- } CH-16.- else CH-17.- { CH-18.- flags = fcntl(1, F_GETFL); CH-19.- if (flags < 0) CH-20.- perror(MYNAME": fcntl(GETFL)"); CH-21.- flags &= ~O_APPEND; CH-22.- if (fcntl(1, F_SETFL, flags) < 0) CH-23.- perror(MYNAME": fcntl(SETFL)"); CH-24.- CH-25.- if (lseek(1, offset, SEEK_SET) < 0) CH-26.- perror(MYNAME": lseek(SET)"); CH-27.- CH-28.- if (write(1, "x", 1) < 0) CH-29.- perror(MYNAME": write(x)"); CH-30.- } CH-31.- return 0; CH-32.- }
Analice el programa CreaHueco. Este programa tiene los dos usos siguientes:
Compile el programa CreaHueco.c y utilícelo para crear un fichero huecos.txt de tamaño 1 byte.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ gcc CreaHueco.c -o CreaHueco alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./CreaHueco 0 > huecos.txt
Mediante el mandato stat podemos obtener las características del fichero.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ stat huecos.txt File: 'huecos.txt' Size: 1 Blocks: 8 IO Block: 131072 regular file Device: fd05h/64773d Inode: 616841 Links: 1 Access: (0644/-rw-r--r--) Uid: (27182/ alumno) Gid: ( 6000/ GrupoAlumno) Access: 2005-12-05 05:35:21.000000000 +0100 Modify: 2005-12-05 08:36:58.000000000 +0100 Change: 2005-12-05 08:36:58.000000000 +0100
Observamos que el tamaño del fichero es 1. Por otro lado, el espacio físico es de 8 bloques de 512 bytes, que equivale a una agrupación de 4KB (según se vio con anterioridad).
Vamos a escribir otra x en la posición 4095, correspondiente al último byte de la primera agrupación. Analizamos los parámetros y el contenido.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ ./CreaHueco 4095 >> huecos.txt alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ stat huecos.txt File: 'huecos.txt' Size: 4096 Blocks: 8 IO Block: 131072 regular file Device: fd05h/64773d Inode: 616841 Links: 1 Access: (0644/-rw-r--r--) Uid: (27182/ alumno) Gid: ( 6000/ GrupoAlumno) Access: 2005-12-05 05:35:21.000000000 +0100 Modify: 2005-12-05 08:42:35.000000000 +0100 Change: 2005-12-05 08:42:35.000000000 +0100
Ahora el tamaño real del fichero es de 4096, aunque los bytes 1 a 4094 no han sido escritos. Con el mandato od observamos el contenido del fichero.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloFicheros$ od -a huecos.txt 0000000 x nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul 0000020 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul * 0007760 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul x 0010000
Observamos que menos los bytes 0 y 4095 todos los demás son nulos. Esto significa que el sistema de ficheros se ha encargado de anular el contenido anterior de los bytes correspondientes, para que no se pueda leer el contenido anterior.
Repita el paso anterior pero con el mandato: ./CreaHueco 8191 » huecos.txt
Sabiendo que 220 = 1048576, ejecute: ./CreaHueco 1048576 > huecos.txt
Sabiendo que 230 = 1073741824, ejecute: ./CreaHueco 1073741824 >huecos.txt
Borre el fichero huecos.txt