Pregunta

Esto parece una pregunta simple, pero no encontré nada similar aquí.

Como no hay una función de copia de archivos en C, tenemos que implementar la copia de archivos nosotros mismos, pero no me gusta reinventar la rueda incluso para cosas triviales como esa, así que me gustaría preguntarle a la nube:

  1. ¿Qué código recomendaría para copiar archivos usando fopen () / fread () / fwrite ()?
    • ¿Qué código recomendaría para copiar archivos usando open () / read () / write ()?

Este código debe ser portátil (windows / mac / linux / bsd / qnx / younameit), estable, probado en el tiempo, rápido, eficiente en la memoria, etc. tamaño del clúster).

Esto parece una pregunta trivial pero, por ejemplo, el código fuente para el comando CP no son 10 líneas de código C.

¿Fue útil?

Solución

En lo que respecta a la E / S real, el código que he escrito un millón de veces en varias formas para copiar datos de una secuencia a otra es algo como esto. Devuelve 0 en caso de éxito o -1 con errno configurado en caso de error (en cuyo caso, podría haberse copiado cualquier número de bytes).

Tenga en cuenta que para copiar archivos normales, puede omitir las cosas de EAGAIN, ya que los archivos regulares siempre bloquean las E / S. Pero inevitablemente, si escribe este código, alguien lo usará en otros tipos de descriptores de archivos, así que considérelo como un obsequio.

Hay una optimización específica de archivo que hace GNU cp , que no me he molestado aquí, que para bloques largos de 0 bytes en lugar de escribir simplemente extiendes el archivo de salida buscando fin.

void block(int fd, int event) {
    pollfd topoll;
    topoll.fd = fd;
    topoll.events = event;
    poll(&topoll, 1, -1);
    // no need to check errors - if the stream is bust then the
    // next read/write will tell us
}

int copy_data_buffer(int fdin, int fdout, void *buf, size_t bufsize) {
    for(;;) {
       void *pos;
       // read data to buffer
       ssize_t bytestowrite = read(fdin, buf, bufsize);
       if (bytestowrite == 0) break; // end of input
       if (bytestowrite == -1) {
           if (errno == EINTR) continue; // signal handled
           if (errno == EAGAIN) {
               block(fdin, POLLIN);
               continue;
           }
           return -1; // error
       }

       // write data from buffer
       pos = buf;
       while (bytestowrite > 0) {
           ssize_t bytes_written = write(fdout, pos, bytestowrite);
           if (bytes_written == -1) {
               if (errno == EINTR) continue; // signal handled
               if (errno == EAGAIN) {
                   block(fdout, POLLOUT);
                   continue;
               }
               return -1; // error
           }
           bytestowrite -= bytes_written;
           pos += bytes_written;
       }
    }
    return 0; // success
}

// Default value. I think it will get close to maximum speed on most
// systems, short of using mmap etc. But porters / integrators
// might want to set it smaller, if the system is very memory
// constrained and they don't want this routine to starve
// concurrent ops of memory. And they might want to set it larger
// if I'm completely wrong and larger buffers improve performance.
// It's worth trying several MB at least once, although with huge
// allocations you have to watch for the linux 
// "crash on access instead of returning 0" behaviour for failed malloc.
#ifndef FILECOPY_BUFFER_SIZE
    #define FILECOPY_BUFFER_SIZE (64*1024)
#endif

int copy_data(int fdin, int fdout) {
    // optional exercise for reader: take the file size as a parameter,
    // and don't use a buffer any bigger than that. This prevents 
    // memory-hogging if FILECOPY_BUFFER_SIZE is very large and the file
    // is small.
    for (size_t bufsize = FILECOPY_BUFFER_SIZE; bufsize >= 256; bufsize /= 2) {
        void *buffer = malloc(bufsize);
        if (buffer != NULL) {
            int result = copy_data_buffer(fdin, fdout, buffer, bufsize);
            free(buffer);
            return result;
        }
    }
    // could use a stack buffer here instead of failing, if desired.
    // 128 bytes ought to fit on any stack worth having, but again
    // this could be made configurable.
    return -1; // errno is ENOMEM
}

Para abrir el archivo de entrada:

int fdin = open(infile, O_RDONLY|O_BINARY, 0);
if (fdin == -1) return -1;

Abrir el archivo de salida es complicado. Como base, desea:

int fdout = open(outfile, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0x1ff);
if (fdout == -1) {
    close(fdin);
    return -1;
}

Pero hay factores de confusión:

  • necesita un caso especial cuando los archivos son iguales, y no recuerdo cómo hacerlo de forma portátil.
  • si el nombre de archivo de salida es un directorio, es posible que desee copiar el archivo en el directorio.
  • si el archivo de salida ya existe (abrir con O_EXCL para determinar esto y verificar EEXIST en caso de error), es posible que desee hacer algo diferente, como lo hace cp -i .
  • es posible que desee que los permisos del archivo de salida reflejen los del archivo de entrada.
  • es posible que desee copiar otros metadatos específicos de la plataforma.
  • es posible que desee o no desvincular el archivo de salida por error.

Obviamente, las respuestas a todas estas preguntas podrían ser "hacer lo mismo que cp " ;. En cuyo caso, la respuesta a la pregunta original es "ignorar todo lo que yo o cualquier otra persona haya dicho, y usar la fuente de cp " ;.

Por cierto, obtener el tamaño del clúster del sistema de archivos es casi inútil. Casi siempre verá que la velocidad aumenta con el tamaño del búfer mucho después de que haya pasado el tamaño de un bloque de disco.

Otros consejos

Esta es la función que uso cuando necesito copiar de un archivo a otro, con el arnés de prueba:

/*
@(#)File:           $RCSfile: fcopy.c,v $
@(#)Version:        $Revision: 1.11 $
@(#)Last changed:   $Date: 2008/02/11 07:28:06 $
@(#)Purpose:        Copy the rest of file1 to file2
@(#)Author:         J Leffler
@(#)Modified:       1991,1997,2000,2003,2005,2008
*/

/*TABSTOP=4*/

#include "jlss.h"
#include "stderr.h"

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_fcopy_c[] = "@(#)$Id: fcopy.c,v 1.11 2008/02/11 07:28:06 jleffler Exp 
#include "stderr.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void err_syserr(const char *fmt, ...)
{
    int errnum = errno;
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(1);
}
quot;; #endif /* lint */ void fcopy(FILE *f1, FILE *f2) { char buffer[BUFSIZ]; size_t n; while ((n = fread(buffer, sizeof(char), sizeof(buffer), f1)) > 0) { if (fwrite(buffer, sizeof(char), n, f2) != n) err_syserr("write failed\n"); } } #ifdef TEST int main(int argc, char **argv) { FILE *fp1; FILE *fp2; err_setarg0(argv[0]); if (argc != 3) err_usage("from to"); if ((fp1 = fopen(argv[1], "rb")) == 0) err_syserr("cannot open file %s for reading\n", argv[1]); if ((fp2 = fopen(argv[2], "wb")) == 0) err_syserr("cannot open file %s for writing\n", argv[2]); fcopy(fp1, fp2); return(0); } #endif /* TEST */

Claramente, esta versión utiliza punteros de archivos de E / S estándar y no descriptores de archivos, pero es razonablemente eficiente y tan portátil como puede ser.


Bueno, excepto la función de error, eso es peculiar para mí. Siempre que maneje los errores limpiamente, debería estar bien. El encabezado " jlss.h " declara fcopy () ; el encabezado " stderr.h " declara err_syserr () entre muchas otras funciones similares de informe de errores. Sigue una versión simple de la función: la real agrega el nombre del programa y hace algunas otras cosas.

<*>

El código anterior puede tratarse como si tuviera una licencia BSD moderna o GPL v3 a su elección.

el tamaño de cada lectura debe ser un múltiplo de 512 (tamaño de sector) 4096 es bueno

Aquí hay un ejemplo muy fácil y claro: Copie un archivo . Dado que está escrito en ANSI-C sin ninguna función particular, creo que este sería bastante portátil.

Dependiendo de lo que quiera decir al copiar un archivo, ciertamente está lejos de ser trivial. Si te refieres a copiar solo el contenido, entonces no hay casi nada que hacer. Pero en general, debe copiar los metadatos del archivo, y eso seguramente depende de la plataforma. No conozco ninguna biblioteca de C que haga lo que quieras de forma portátil. Simplemente manejar el nombre de archivo por sí mismo no es un asunto trivial si le importa la portabilidad.

En C ++, existe la biblioteca de archivos en impulsar

Una cosa que encontré al implementar mi propia copia de archivo, y parece obvio, pero no lo es: las E / S son lentas . Puedes calcular la velocidad de tu copia según la cantidad que hagas. Claramente, debe hacer la menor cantidad posible de ellos.

Los mejores resultados que encontré fueron cuando obtuve un búfer ginourmous, leí todo el archivo fuente en una E / S, luego escribí todo el búfer en una E / S. Si incluso tuve que hacerlo en 10 lotes, se hizo muy lento. Intentar leer y escribir cada byte, como podría intentarlo primero un codificador ingenuo, fue doloroso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top