Испытанный и правда простой код копирования файлов в C? - PullRequest
9 голосов
/ 17 июня 2009

Это выглядит как простой вопрос, но я не нашел ничего подобного здесь.

Поскольку в C нет функции копирования файлов, мы должны сами копировать файлы, но я не люблю изобретать велосипед даже для таких простых вещей, как этот, поэтому я бы хотел спросить облако:

  1. Какой код вы бы порекомендовали для копирования файлов с использованием fopen () / fread () / fwrite ()?
    • Какой код вы бы порекомендовали для копирования файлов с использованием open () / read () / write ()?

Этот код должен быть переносимым (windows / mac / linux / bsd / qnx / younameit), стабильным, проверенным временем, быстрым, эффективным с точки зрения памяти и т. Д. Приветствуется проникновение во внутренние компоненты конкретной системы для повышения производительности (например, получение файловой системы). размер кластера).

Это кажется тривиальным вопросом, но, например, исходный код для команды CP не состоит из 10 строк кода C.

Ответы [ 6 ]

5 голосов
/ 18 июня 2009

Это функция, которую я использую, когда мне нужно скопировать из одного файла в другой - с тестовым набором:

/*
@(#)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 $";
#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 */

Очевидно, что в этой версии используются файловые указатели из стандартного ввода-вывода, а не файловые дескрипторы, но она достаточно эффективна и настолько переносима, насколько это возможно.


Ну, кроме функции ошибок - это свойственно мне. Пока вы правильно обрабатываете ошибки, вы должны быть в порядке. Заголовок "jlss.h" объявляет fcopy(); заголовок "stderr.h" объявляет err_syserr() среди многих других аналогичных функций сообщения об ошибках. Далее следует простая версия функции - настоящая добавляет имя программы и выполняет другие действия.

#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);
}

Приведенный выше код может рассматриваться как имеющий современную лицензию BSD или GPL v3 по вашему выбору.

3 голосов
/ 17 июня 2009

Что касается фактического ввода-вывода, то код, который я написал миллион раз в разных ипостасях для копирования данных из одного потока в другой, выглядит примерно так. В случае успеха возвращается 0 или -1 с ошибкой, установленной в случае ошибки (в этом случае может быть скопировано любое количество байтов).

Обратите внимание, что для копирования обычных файлов вы можете пропустить EAGAIN, поскольку обычные файлы всегда блокируют ввод / вывод. Но неизбежно, если вы напишите этот код, кто-то будет использовать его для файловых дескрипторов других типов, так что считайте его халявой.

Существует оптимизация для конкретного файла, которую выполняет GNU cp, которую я здесь не беспокоил, что для длинных блоков по 0 байтов вместо записи вы просто расширяете выходной файл, ища конец.

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
}

Чтобы открыть входной файл:

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

Открыть выходной файл довольно сложно. В качестве основы вы хотите:

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

Но есть смешивающие факторы:

  • вам нужен специальный случай, когда файлы одинаковы, и я не могу вспомнить, как это сделать переносимо.
  • если выходное имя файла является каталогом, вы можете скопировать файл в каталог.
  • если выходной файл уже существует (откройте его с помощью O_EXCL, чтобы определить это и проверьте EEXIST на наличие ошибок), вы можете захотеть сделать что-то другое, как cp -i.
  • вам может потребоваться, чтобы разрешения выходного файла отражали разрешения входного файла.
  • возможно, вы захотите скопировать другие метаданные для конкретной платформы.
  • вы можете, а можете и не захотеть отсоединять выходной файл при ошибке.

Очевидно, что ответы на все эти вопросы могут быть "сделать так же, как cp". В этом случае ответ на первоначальный вопрос звучит так: «игнорируйте все, что я или кто-либо еще сказал, и используйте источник cp».

Кстати, получение размера кластера файловой системы практически бесполезно. Вы почти всегда увидите увеличение скорости при увеличении размера буфера после того, как пройдете размер блока диска.

2 голосов
/ 26 октября 2009

размер каждого чтения должен быть кратным 512 (размер сектора) 4096 является хорошим

1 голос
/ 17 июня 2009

Одна вещь, которую я обнаружил при реализации своей собственной копии файла, и это кажется очевидным, но это не так: ввод / вывод медленный . Вы можете в значительной степени оценить скорость своей копии по тому, сколько из них вы делаете. Очевидно, вам нужно сделать как можно меньше из них.

Наилучшие результаты, которые я обнаружил, были, когда я получил себе буфер ginourmous, прочитал весь исходный файл в него за один ввод-вывод, а затем записал весь буфер обратно из него за один ввод-вывод. Если бы мне даже пришлось сделать это за 10 партий, это стало слишком медленным. Попытка прочитать и выписать каждый байт, как может сначала попытаться сделать наивный кодер, была просто болезненной.

1 голос
/ 17 июня 2009

В зависимости от того, что вы подразумеваете под копированием файла, это, безусловно, далеко не тривиально. Если вы имеете в виду только копирование контента, то тут почти нечего делать. Но, как правило, вам нужно скопировать метаданные файла, и это, безусловно, зависит от платформы. Я не знаю ни одной библиотеки C, которая делает то, что вы хотите, в переносимой форме. Сама обработка имени файла не является тривиальным вопросом, если вы заботитесь о переносимости.

В C ++ есть библиотека файлов в boost

1 голос
/ 17 июня 2009

Вот очень простой и понятный пример: Скопируйте файл . Поскольку он написан на ANSI-C без каких-либо особых вызовов функций, я думаю, что этот будет в значительной степени переносимым.

...