cc31a Desarrollo de Software de Sistemas

Patricio Poblete (ppoblete@dcc.uchile.cl)

Entrada/Salida en Unix

  1. Manejo de Archivos
  2. Pipes
  3. Manejo de Directorios

Manejo de Archivos

Unix provee una interfaz uniforme,en que todos los dispositivos se ven como archivos:


        /home/ppoblete/x
        /dev/tty1

Al abrir un archivo en Unix, se recibe un número entero pequeño mayor o igual que cero, que se llama un file descriptor (fd). (Nótese que esto es distinto de lo que ocurre al abrir un archivo en la biblioteca standard de C, en que se recibe un file pointer.)

Este fd identifica al archivo abierto para todas la operaciones (leer, escribir, etc.).

Todo programa comienza con tres fds ya abiertos:


        0  stdin
        1  stdout
        2  stderr

Las operaciones básicas de entrada/salida son:


        nread = read(fd, buf, nbytes);

        nwritten = write(fd, buf, nbytes);

Cada llamada retorna el número de bytes transferidos. En el caso del write es un error que éste no sea igual al número pedido. En caso de fin de archivo se retorna un cero y, en caso de error, un -1.

copia.c


        /* Copia stdin -> stdout */
        #include <stdio.h>
        #define BUFSIZE 4096
        
        main()
          {
            char buf[BUFSIZE];
            int n;
        
            while((n=read(0, buf, BUFSIZE))>0)
                write(1, buf, n);
        
            return 0;
          }

Estas funciones también se pueden usar para construir funciones de más alto nivel, como por ejemplo:

getchar1.c


        #include <stdio.h>
        
        int getchar()
          {
            char c;
        
            return (read(0, &c, 1)==1? (unsigned char)c : EOF);
          }
        
        main()
          {
            int c;
        
            while((c=getchar())!=EOF)
                putchar(c);
          }

Por motivos de eficiencia, es mejor no leer de a un byte, sino con un buffer:

getchar2.c


        #include <stdio.h>
        
        int getchar()
          {
            static char buf[BUFSIZ];
            static char *p=buf;
            static int n=0;
        
            if(n==0) /* buffer vacío */
              {
                n=read(0,buf, sizeof buf);
                p=buf;
              }
        
            return (--n>=0? (unsigned char)*p++ : EOF);
          }
        
        main()
          {
            int c;
        
            while((c=getchar())!=EOF)
                putchar(c);
          }

Abrir y cerrar archivos


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

    fd=open(path, flags, perms);
    /* flags puede ser O_RDONLY, O_WRONLY, O_RDWR */
    /* Además se le puede superponer los flags siguientes:
        O_APPEND      ir al final antes de cada write
        O_CREAT       crea el archivo si no existe
        O_EXCL        junto con O_CREAT, falla si el archivo ya existe
        O_TRUNC       trunca el archivo a largo cero

       El parámetro perms se usa solo junto con O_CREAT
    */

    close(fd);

La llamada


    fd=creat(path, perms);

crea el archivo con los permisos indicados y equivale a

    fd=open(path, O_WRONLY|O_CREAT|O_TRUNC, perms);

Permisos


     owner group other

     r w x r w x r w x

Ejemplos:


     0755   rwx para owner, rx para los demás

     0666   rw para todos

my_cp.c


        /* Uso: my_cp from to */
        #include <stdio.h>
        #include <stdarg.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
        
        void error(char *fmt, ...)
          {
            va_list args;
        
            va_start(args, fmt);
            fprintf(stderr, "Error: ");
            vfprintf(stderr, fmt, args);
            fprintf(stderr, "\n");
            va_end(args);
        
            exit(1);
          }
        
        main(int argc, char *argv[])
          {
            int f1, f2, n;
            char buf[BUFSIZ];
        
            if(argc!=3)
                error("Use: my_cp from to");
        
            if((f1=open(argv[1], O_RDONLY))==-1)
                error("Can't open %s", argv[1]);
        
            if((f2=creat(argv[2], 0666))==-1)
                error("Can't open %s", argv[2]);
            /* usando stats se pueden mantener los permisos */
        
            while((n=read(f1, buf, BUFSIZ))>0)
                if(write(f2, buf, n)!=n)
                    error("Write error on file %s", argv[2]);
        
            return 0;
          }

Borrar un archivo


    unlink(path);

Acceso directo


    lseek(fd, offset, how);

Ejemplos:

        lseek(fd, 0L, SEEK_EOF);  /* para append */
        
        lseek(fd, 0L, SEEK_SET);  /* rewind */

lseek retorna la nueva posición dentro del archivo, o -1 si hubo un error.

Pipes

Un pipe es un canal de comunicación entre dos procesos. Se comporta como una cola (FIFO) en donde lo que se escribe por un extremo se lee por el otro.

Para crear un pipe se hace


        pipe(fds);
en donde fds es un arreglo de tamaño 2. Después de ejecutar esta llamada, se obtienen 2 fds:

        fds[0] permite leer
        fds[1] permite escribir

Este arreglo generalmente se entrega a dos procesos, uno de los cuales cierra de inmediato el fd de lectura y el otro cierra el fd de escritura.

El sistema garantiza que un write de tamaño menor o igual a PIPE_BUF es indivisible.

Los ejemplos de uso los veremos al estudiar el funcionamiento de los procesos en Unix.

Manejo de Directorios

Los directorios son archivos especiales que contienen listas de archivos en su interior. Para su manejo existen funciones especiales.


    #include <direcnt.h>

    /* Esto define un "struct dirent" que incluye un

           char d_name[NAME_MAX];
    */

    DIR *dirp;

    dirp=opendir(dirname); /* NULL en caso de error */

    struct dirent *d;

    d=readdir(dirp); /* entrega siguiente nombre de archivo
                        en d->d_name, retorna NULL al final */

    closedir(dirp);

    rewinddir(dirp); /* vuelve al inicio */

    buf=getcwd(buf, size); /* guarda en buf pathname absoluto del
                              directorio actual, retorna NULL si error */

    status=chdir(path); /* 0 => OK, -1 => error */

    mkdir(path, perms);

    rmdir(path);

    link(oldpath, newpath); /* no funciona para directorios */

    unlink(path);

    rename(oldpath, newpath);

Características de un archivo

Para averiguar atributos de un archivo, se usa la funcion:


    stat(path, buf); /* buf es de tipo "struct stat *" */

La estructura stat tiene campos tales como:


        st_mode             modo (=perms)
        st_ino              inode
        st_dev              device
        st_nlink            número de links
        st_uid              user id del owner
        st_gid              grupo
        st_size             tamaño del archivo en bytes
        st_atime            dia y hora del último acceso
        st_ctime            día y hora del último cambio (p.ej. perms)
        st_mtime            día y hora de la última modificación

Hay macros para "testear"el modo:


        S_ISDIR             directorio?
        S_ISCHR             char special device?
        S_ISBLK             block special device?
        S_ISREG             regular file?
        S_ISFIFO            named pipe?

Los atributos se pueden modificar con:


        chmod(path, perms);
        
        chown(path, owner, group);

También es posiblefalsificar el "time stamp" de un archivo, con la función utime (ver manual). ¿Hay un uso legítimo para esto?

Ejemplo: Listar directorios recursivamente.

listdir.c


        #include <stdio.h>
        #include <sys/types.h>
        #include <dirent.h>
        #include <sys/stat.h>
        #include <stdlib.h>
        #include <string.h>
        #include <unistd.h>
        
        #define MAX_PATH 1000
        
        void listdir(int indent, char *name)
          {
            DIR *current_dir;
            struct dirent *this_entry;
            char cwd[MAX_PATH+1];
            int i;
            struct stat status;
        
            /* imprimimos directorio actual */
            for(i=1; i<=indent; ++i)
                printf("    ");
            printf("%s\n", name);
        
            if(stat(name, &status)!=0)
                error("Can't stat %s", name);
            if(!S_ISDIR(status.st_mode))
                return;
        
            /* abrimos el directorio para lectura */
            if((current_dir=opendir(name))==NULL)
                error("Can't open directory %s", name);
        
            if(getcwd(cwd, MAX_PATH+1)==NULL)
                error("Can't get cwd");
            if(chdir(name)!=0)
                error("Can't chdir");
        
            while((this_entry=readdir(current_dir))!=NULL)
              {
                if(strcmp(this_entry->d_name, ".")==0
                || strcmp(this_entry->d_name, "..")==0)
                    continue;
        
                listdir(indent+1, this_entry->d_name);
              }
        
            closedir(current_dir);
            chdir(cwd);
          }
        
        main()
          {
            listdir(0, ".");
          }