En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

martes, 25 de abril de 2017

The FileCopy
cómo escribir una función de File Copy en C - pt.2

Ok, re-empezamos de donde nos dejamos en el último post (¿lo habeis leido verdad?) Y, como prometido, en esta ocasión el tema será una versión con buffered I/O de la función cpFile(). Es deber, antes, proporcionar otra imagen de The Thing, si no, de lo contrario, podría parecer que esto es sólo un blog de programación, mientras que, como bien sabéis, es un blog para programadores cinefilos...
...con un sombrero así se programa mejor...
Vale, repetimos: I/O buferizado (y por lo tanto, por ejemplo, fread(3) en lugar de read(2), porque el objetivo esta vez es la portabilidad ¿y que hay más portátil (en C) que usar el contenido de stdio.h? El código que veremos en un momento utiliza (casi) el mismo main() de la versión unbuffered y los únicos cambios son internos a la función cpFile(). De hecho, incluso la cpFile() es casi idéntica desde un punto de vista lógico, ya que las funciones buffered tienen una sintaxis de uso y un funcionamiento muy similar a las equivalentes versiones unbuffered. Y claro, si la versión buffered os sale muy diferente (visualmente y lógicamente) de la versión unbuffered es que hay algo que no funciona... bueno, sobre este punto voy a hacer un pequeña digresión al final del post. Por ahora: ¡vamos con el código!
#include <stdio.h>
#include <stdlib.h>

// prototipos locales
static int cpFile(const char* src, const char* dest);

// función main()
int main(int argc, char *argv[])
{
    // test argumentos
    if (argc != 3) {
        // error args
        printf("%s: wrong arguments counts\n", argv[0]);
        printf("usage: %s srcfile destfile [e.g.: %s try.c try.save]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // ejecuta copia
    int retval;
    if ((retval = cpFile(argv[2], argv[1])) < 0) {
        // enseña error y sale con error
        fprintf(stderr, "%s: error: %d\n", argv[0], retval);
        exit(EXIT_FAILURE);
    }

    // sale con Ok
    return EXIT_SUCCESS;
}

// función cpFile()
static int cpFile(
    const char *dest,               // file destinación
    const char *src)                // file fuente
{
    // abre el file fuente
    FILE *fp_in;
    if ((fp_in = fopen(src, "r")) == NULL) {
        // return con error
        return -1;
    }

    // abre el file destinación
    FILE *fp_out;
    if ((fp_out = fopen(dest, "w")) == NULL) {
        // cierra el file y return con error
        fclose(fp_in);
        return -2;
    }

    // r/w loop para la copia usando buffered I/O
    size_t n_read;
    char buffer[BUFSIZ];
    while ((n_read = fread(buffer, 1, sizeof(buffer), fp_in)) > 0) {
        if (! ferror(fp_in)) {
            // write buffer
            fwrite(buffer, 1, n_read, fp_out);
            if (ferror(fp_out)) {
                // cierra los file y return con error
                fclose(fp_in);
                fclose(fp_out);
                return -3;
            }
        }
        else {
            // cierra los file y return con error
            fclose(fp_in);
            fclose(fp_out);
            return -4;
        }
    }

    // cierra los file
    fclose(fp_in);
    fclose(fp_out);

    // return con Ok
    return 0;
}
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales. El main(), como se había anticipado, es prácticamente idéntico, mientras que en la cpFile() se repiten, exactamente, las operaciones de la version unbuffered, pero usando fopen(3) en lugar de  open(2), fclose(3) en lugar de close(2), etc. ¿Donde encontramos algunas (pequeñas) diferencias? Sólo en el test de (eventuales) errores de lectura/escritura, que en la versión unbuffered estaban implícitas en las operaciones de read/write (probando si el resultado era igual a -1), mientras que en este caso hay que usar una función a parte, ferror(3), y esto es debido a que:
    On  success,  fread()  and  fwrite() return the number of items read or
    written.  This number equals the number of bytes transferred only  when
    size  is 1.  If an error occurs, or the end of the file is reached, the
    return value is a short item count (or zero).
    fread() does not distinguish between end-of-file and error, and callers
    must use feof(3) and ferror(3) to determine which occurred.
Lo anterior es lo que trae la man-page de fread(3)/fwrite(3), y creo que es una justificación suficiente de por qué he escrito el código asì (y, por lo mismo, no podemos usar strerror(3) en el main() para enseñar los errores). Así que, como se había anticipado, las versiones buffered y unbuffered deben ser casi sobreponibles, y creo que es exactamente el resultado conseguido.

Y ahora la digresión prometida: me ha pasado de encontrar en la red (incluso en apreciados blogs/webs de programación) ejemplos de buffered-copy de file que utilizan unos loop de este tipo:
while (!feof(fp_in)) {
    // lee y escribe buffer
    ...
}
vale, ¿cómo se puede comentar esto? Con una sola palabra:

NO

Si uno escribe el loop de esta manera significa que no ha leído la man-page de fread(3)/fwrite(3) o que la ha leido y no ha entendido el contenido. No hay necesidad de reinventar la rueda, repito: fread(3)/fwrite(3) funcionan casi de la misma manera de read(2)/write(2), por lo que, si el ejemplo de cpFile() unbuffered del post anterior era bueno (¡y lo era!), entonces el ejemplo del post actual debería ser (casi) idéntico. El loop con un while() que testea feof(3) es sintácticamente correcto, pero no lo es lógicamente, ya que comienza testeando algo que todavía no es utilizable (uhmm, ¿una prueba predictiva?) y que, además, no hay necesidad de testear. Bah, no quiero extender demasiado este argumento y os remito a la excelente análisis contenida ahí (en el siempre optimo stackoverflow.com).
Obviamente espero no haber ofendido a nadie (con la digresión anterior): Recordar que errare humanum est... y, por supuesto, también en este blog habré escrito en el pasado algunas tonterías (espero no graves como aquella enseñada hace un momento). Os aseguro, sin embargo, que yo siempre soy muy cuidadoso de no proponer soluciones que no he tenido tiempo para escribir y probar de manera curada, o de lo contrario en lugar de un blog de programación artística este sería un blog de programación a la esperamos que funciona...

¡Hasta el próximo post!