Pergunta

Isto parece uma pergunta simples, mas eu não encontrei nada parecido aqui.

Uma vez que não há nenhuma função de cópia de arquivos em C, temos que implementar arquivo de copiar a nós mesmos, mas eu não gosto de reinventar a roda, mesmo para coisas triviais como esse, então eu gostaria de perguntar a nuvem:

  1. O código que você recomendaria para arquivo copiando usando fopen () / fread () / fwrite ()?
    • O código que você recomendaria para arquivo copiando usando open () / ler () / write ()?

Este código deve ser portátil (Windows / Mac / Linux / BSD / qnx / younameit), estável, tempo testado, rápido, eficiente de memória e etc. Metendo internos do sistema específico para espremer um pouco mais de desempenho é bem-vinda (como a obtenção de sistema de arquivos tamanho do cluster).

Esta parece ser uma questão trivial, mas, por exemplo, o código fonte para o comando CP não é 10 linhas de código C.

Foi útil?

Solução

Quanto ao / s real vai, o código que eu escrevi um milhão de vezes em vários disfarces para copiar dados de um fluxo para outro é algo como isto. Ele retorna 0 em caso de sucesso, ou -1 com errno conjunto em caso de erro (caso em que qualquer número de bytes podem ter sido copiados).

Note que para copiar arquivos regulares, você pode pular o material EAGAIN, já que os arquivos regulares são sempre bloqueando I / O. Mas, inevitavelmente, se você escrever este código, alguém vai utilizá-lo em outros tipos de descritores de arquivos, por isso considero-o um brinde.

Há uma otimização específica de arquivo que GNU cp faz, que eu não tenha incomodado com aqui, que por longos blocos de 0 bytes em vez de escrever que você acabou de estender o arquivo de saída, procurando fora da final.

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 o arquivo de entrada:

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

A abertura do arquivo de saída é tricksy. Como base, você quer:

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

Mas há fatores de confusão:

  • você precisa caso especial quando os arquivos são os mesmos, e eu não me lembro como fazer isso portably.
  • Se o nome do arquivo de saída é um diretório, você pode querer copiar o arquivo para o diretório.
  • Se o arquivo de saída já existe (aberto com O_EXCL para determinar isso e verificar se há EEXIST em caso de erro), você pode querer fazer algo diferente, como cp -i faz.
  • você pode querer as permissões do arquivo de saída para refletir as do arquivo de entrada.
  • você pode querer outros meta-dados específicos de plataforma a ser copiado.
  • você pode ou não pode querer desvincular o arquivo de saída em caso de erro.

Obviamente, as respostas a todas essas perguntas poderia ser "fazer o mesmo que cp". Caso em que a resposta à pergunta inicial é "ignorar tudo o que eu ou qualquer outra pessoa tenha dito, e usar a fonte de cp".

Btw, ficando tamanho do cluster do sistema de arquivos é quase inútil. Você verá quase sempre velocidade crescente com o tamanho do buffer muito tempo depois que você passou do tamanho de um bloco de disco.

Outras dicas

Esta é a função que eu uso quando eu preciso copiar um arquivo para outro - com equipamento de teste:

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

Claramente, esta versão utiliza ponteiros de arquivo de descritores O e não de arquivo / I padrão, mas é razoavelmente eficiente e tão portátil quanto possível.


Bem, exceto a função de erro - que é peculiar para mim. Contanto que você lida com erros de forma limpa, você deve estar OK. O cabeçalho "jlss.h" declara fcopy(); o cabeçalho "stderr.h" declara err_syserr() entre muitas outras funções de relatório de erro semelhantes. A versão simples da função segue - o real acrescenta o nome do programa e faz algumas outras coisas

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

O código acima pode ser considerado como tendo uma licença BSD moderno ou GPL v3 em sua escolha.

o tamanho de cada necessidade de leitura para ser um múltiplo de 512 (tamanho do setor) 4096 é uma boa

Aqui está um exemplo muito fácil e clara: copiar um arquivo . Uma vez que é escrito em ANSI-C sem quaisquer chamadas de função particulares acho que este seria praticamente portátil.

Dependendo do que você quer dizer com a cópia de um arquivo, ele é certamente longe de ser trivial. Se você quer dizer copiar apenas o conteúdo, então não há quase nada para fazer. Mas, geralmente, você precisa copiar os metadados do arquivo, e isso é certamente dependente de plataforma. Eu não sei de qualquer biblioteca C que faz o que quiser de uma forma portátil. Apenas lidar com o nome do arquivo por si só não é uma questão trivial, se você se preocupa com a portabilidade.

Em C ++, há a biblioteca de arquivos em impulsionar

Uma coisa que eu encontrei quando implementar a minha própria cópia de arquivo, e parece óbvio, mas não é: I / O são lento . Você pode muito bem o tempo da velocidade de sua cópia por quantos deles você faz. Então, claramente, que você precisa fazer como alguns deles quanto possível.

Os melhores resultados que eu encontrei eram quando eu próprio tenho um buffer ginourmous, ler todo o arquivo de origem para ele em um I / O, em seguida, escreveu toda a volta tampão fora dele em um I / O. Se eu mesmo tive que fazê-lo em 10 lotes, ficou maneira lenta. Tentando ler e escrever cada byte, como um naieve codificador pode tentar primeiro, era apenas doloroso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top