Cでコードをコピーする単純なファイルを試してみましたか?

これは、あるファイルから別のファイルにコピーする必要があるときに使用する関数です - テスト ハーネス:

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

明らかに、このバージョンはファイル記述子ではなく、標準 I/O からのファイル ポインターを使用しますが、適度に効率的であり、可能な限り移植可能です。

まあ、エラー機能を除いて - それは私に特有のものです。エラーをきれいに処理する限り、問題はありません。 "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 を持つものとして扱われる場合があります。


実際の I/O に関する限り、あるストリームから別のストリームにデータをコピーするためにさまざまな形で 100 万回も書いたコードは、次のようになります。成功した場合は 0 を返し、エラーの場合は errno を設定して -1 を返します (この場合、任意の数のバイトがコピーされた可能性があります)。

通常のファイルをコピーする場合、通常のファイルは常に I/O をブロックするため、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 のソースを使用する」です。 ".

ところで、ファイルシステムのクラスターサイズを取得することはほとんど役に立ちません。ほとんどの場合、ディスク ブロックのサイズを超えてからしばらくすると、バッファ サイズに応じて速度が向上します。


各読み取りのサイズは、512 (セクター サイズ) の倍数である必要があります。4096 が適切です。