web-dev-qa-db-ja.com

Cを使用してUnixでファイルをコピーするにはどうすればよいですか?

私はWin32の CopyFile に相当するUnixを探しています。独自のバージョンを書くことで車輪を再発明したくありません。

50
Motti

sendfileのような移植性のないAPIを呼び出す必要も、外部ユーティリティにシェルアウトする必要もありません。 70年代に機能していた同じ方法が今でも機能します。

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int cp(const char *to, const char *from)
{
    int fd_to, fd_from;
    char buf[4096];
    ssize_t nread;
    int saved_errno;

    fd_from = open(from, O_RDONLY);
    if (fd_from < 0)
        return -1;

    fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666);
    if (fd_to < 0)
        goto out_error;

    while (nread = read(fd_from, buf, sizeof buf), nread > 0)
    {
        char *out_ptr = buf;
        ssize_t nwritten;

        do {
            nwritten = write(fd_to, out_ptr, nread);

            if (nwritten >= 0)
            {
                nread -= nwritten;
                out_ptr += nwritten;
            }
            else if (errno != EINTR)
            {
                goto out_error;
            }
        } while (nread > 0);
    }

    if (nread == 0)
    {
        if (close(fd_to) < 0)
        {
            fd_to = -1;
            goto out_error;
        }
        close(fd_from);

        /* Success! */
        return 0;
    }

  out_error:
    saved_errno = errno;

    close(fd_from);
    if (fd_to >= 0)
        close(fd_to);

    errno = saved_errno;
    return -1;
}
51
caf

APIには、焼き付けられた同等のCopyFile関数はありません。ただし、 sendfile を使用してファイルをカーネルモードでコピーすることができます。これは、ファイルを開いてループしてバッファに読み取り、出力を書き込むよりも高速で優れたソリューションです(さまざまな理由により)。別のファイル。

更新:

Linuxカーネルバージョン2.6.33では、sendfileの出力をソケットにする必要があるという制限が解除され、元のコードはLinuxとOS X 10.9 Mavericksの両方で機能しますが、sendfileでは、出力がソケットである必要があり、コードは機能しません。

次のコードスニペットは、ほとんどのOS X(10.5以降)、(Free)BSD、およびLinux(2.6.33以降)で動作します。実装は、すべてのプラットフォームで「ゼロコピー」です。つまり、すべてがカーネルスペースで行われ、ユーザースペースに出入りするバッファーやデータのコピーはありません。あなたが得ることができる最高のパフォーマンス。

_#include <fcntl.h>
#include <unistd.h>
#if defined(__Apple__) || defined(__FreeBSD__)
#include <copyfile.h>
#else
#include <sys/sendfile.h>
#endif

int OSCopyFile(const char* source, const char* destination)
{    
    int input, output;    
    if ((input = open(source, O_RDONLY)) == -1)
    {
        return -1;
    }    
    if ((output = creat(destination, 0660)) == -1)
    {
        close(input);
        return -1;
    }

    //Here we use kernel-space copying for performance reasons
#if defined(__Apple__) || defined(__FreeBSD__)
    //fcopyfile works on FreeBSD and OS X 10.5+ 
    int result = fcopyfile(input, output, 0, COPYFILE_ALL);
#else
    //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+
    off_t bytesCopied = 0;
    struct stat fileinfo = {0};
    fstat(input, &fileinfo);
    int result = sendfile(output, input, &bytesCopied, fileinfo.st_size);
#endif

    close(input);
    close(output);

    return result;
}
_

[〜#〜] edit [〜#〜]:必要に応じて、宛先の開始をcreat()への呼び出しに置き換えました指定されるフラグ_O_TRUNC_以下のコメントを参照してください。

21

Fork/execlを使用してcpを実行し、作業を行うのは簡単です。これには、Bobby Tables攻撃を受けにくく、引数を同程度にサニタイズする必要がないという点で、システムよりも優れています。さらに、system()ではコマンド引数をまとめて使用する必要があるため、sloppy sprintf()チェックによるバッファオーバーフローの問題は発生しません。

Cpを記述するのではなく、直接呼び出す利点は、宛先に存在するターゲットパスの要素を心配する必要がないことです。独自のロールコードでこれを行うと、エラーが発生しやすく、面倒です。

この例をANSI Cで記述し、単純なコードであるということ以外に、最もエラーの少ない処理のみを書き留めました。

void copy(char *source, char *dest)
{
    int childExitStatus;
    pid_t pid;
    int status;
    if (!source || !dest) {
        /* handle as you wish */
    }

    pid = fork();

    if (pid == 0) { /* child */
        execl("/bin/cp", "/bin/cp", source, dest, (char *)0);
    }
    else if (pid < 0) {
        /* error - couldn't start process - you decide how to handle */
    }
    else {
        /* parent - wait for child - this has all error handling, you
         * could just call wait() as long as you are only expecting to
         * have one child process at a time.
         */
        pid_t ws = waitpid( pid, &childExitStatus, WNOHANG);
        if (ws == -1)
        { /* error - handle as you wish */
        }

        if( WIFEXITED(childExitStatus)) /* exit code in childExitStatus */
        {
            status = WEXITSTATUS(childExitStatus); /* zero is normal exit */
            /* handle non-zero as you wish */
        }
        else if (WIFSIGNALED(childExitStatus)) /* killed */
        {
        }
        else if (WIFSTOPPED(childExitStatus)) /* stopped */
        {
        }
    }
}
20
plinth

通常のPOSIX呼び出しを使用し、ループなしのコピー関数の別のバリアント。 cafの回答のバッファーコピーバリアントからヒントを得たコード。警告:mmapを使用すると、32ビットシステムでは簡単に失敗する可能性がありますが、64ビットシステムでは危険はほとんどありません。

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>

int cp(const char *to, const char *from)
{
  int fd_from = open(from, O_RDONLY);
  if(fd_from < 0)
    return -1;
  struct stat Stat;
  if(fstat(fd_from, &Stat)<0)
    goto out_error;

  void *mem = mmap(NULL, Stat.st_size, PROT_READ, MAP_SHARED, fd_from, 0);
  if(mem == MAP_FAILED)
    goto out_error;

  int fd_to = creat(to, 0666);
  if(fd_to < 0)
    goto out_error;

  ssize_t nwritten = write(fd_to, mem, Stat.st_size);
  if(nwritten < Stat.st_size)
    goto out_error;

  if(close(fd_to) < 0) {
    fd_to = -1;
    goto out_error;
  }
  close(fd_from);

  /* Success! */
  return 0;
}
out_error:;
  int saved_errno = errno;

  close(fd_from);
  if(fd_to >= 0)
    close(fd_to);

  errno = saved_errno;
  return -1;
}

[〜#〜] edit [〜#〜]:ファイル作成のバグを修正しました。 http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/21​​80157#2180157 のコメントを参照してください。

5
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);

system( cmd);

エラーチェックを追加...

それ以外の場合は、両方を開いて読み取り/書き込みでループしますが、おそらく必要なものではありません。

...

有効なセキュリティ上の懸念に対処するための更新:

「system()」を使用するのではなく、fork/waitを実行し、子でexecv()またはexecl()を呼び出します。

execl( "/bin/cp", "-p", old, new);
3
Roboprog

これを行う方法があります。system呼び出しに頼らずに、次のようなラッパーを組み込む必要があります。

#include <sys/sendfile.h>
#include <fcntl.h>
#include <unistd.h>

/* 
** http://www.unixguide.net/unix/programming/2.5.shtml 
** About locking mechanism...
*/

int copy_file(const char *source, const char *dest){
   int fdSource = open(source, O_RDWR);

   /* Caf's comment about race condition... */
   if (fdSource > 0){
     if (lockf(fdSource, F_LOCK, 0) == -1) return 0; /* FAILURE */
   }else return 0; /* FAILURE */

   /* Now the fdSource is locked */

   int fdDest = open(dest, O_CREAT);
   off_t lCount;
   struct stat sourceStat;
   if (fdSource > 0 && fdDest > 0){
      if (!stat(source, &sourceStat)){
          int len = sendfile(fdDest, fdSource, &lCount, sourceStat.st_size);
          if (len > 0 && len == sourceStat.st_size){
               close(fdDest);
               close(fdSource);

               /* Sanity Check for Lock, if this is locked -1 is returned! */
               if (lockf(fdSource, F_TEST, 0) == 0){
                   if (lockf(fdSource, F_ULOCK, 0) == -1){
                      /* WHOOPS! WTF! FAILURE TO UNLOCK! */
                   }else{
                      return 1; /* Success */
                   }
               }else{
                   /* WHOOPS! WTF! TEST LOCK IS -1 WTF! */
                   return 0; /* FAILURE */
               }
          }
      }
   }
   return 0; /* Failure */
}

上記のサンプル(エラーチェックは省略!)では、openclose、およびsendfileを使用しています。

編集:Ascafは、競合状態openstatの間に発生する可能性があることを指摘したので、これをもう少し堅牢にすると思いました。 ..ロック機構はプラットフォームごとに異なることに留意してください... Linuxでは、lockfを使用したこのロック機構で十分です。これをポータブルにしたい場合は、#ifdef異なるプラットフォーム/コンパイラを区別するマクロ...これを発見してくれたcafに感謝します...「ユニバーサルロックルーチン」を生成したサイトへのリンクがあります here

3
t0mm13b

1つのオプションは、system()を使用してcpを実行できることです。これは、cp(1)コマンドを再利用して作業を行うだけです。別のリンクをファイルに作成するだけでよい場合は、link()またはsymlink()を使用して実行できます。

#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

#define    print_err(format, args...)   printf("[%s:%d][error]" format "\n", __func__, __LINE__, ##args)
#define    DATA_BUF_SIZE                (64 * 1024)    //limit to read maximum 64 KB data per time

int32_t get_file_size(const char *fname){
    struct stat sbuf;

    if (NULL == fname || strlen(fname) < 1){
        return 0;
    }

    if (stat(fname, &sbuf) < 0){
        print_err("%s, %s", fname, strerror(errno));
        return 0;
    }

    return sbuf.st_size; /* off_t shall be signed interge types, used for file size */
}

bool copyFile(CHAR *pszPathIn, CHAR *pszPathOut)
{
    INT32 fdIn, fdOut;
    UINT32 ulFileSize_in = 0;
    UINT32 ulFileSize_out = 0;
    CHAR *szDataBuf;

    if (!pszPathIn || !pszPathOut)
    {
        print_err(" Invalid param!");
        return false;
    }

    if ((1 > strlen(pszPathIn)) || (1 > strlen(pszPathOut)))
    {
        print_err(" Invalid param!");
        return false;
    }

    if (0 != access(pszPathIn, F_OK))
    {
        print_err(" %s, %s!", pszPathIn, strerror(errno));
        return false;
    }

    if (0 > (fdIn = open(pszPathIn, O_RDONLY)))
    {
        print_err("open(%s, ) failed, %s", pszPathIn, strerror(errno));
        return false;
    }

    if (0 > (fdOut = open(pszPathOut, O_CREAT | O_WRONLY | O_TRUNC, 0777)))
    {
        print_err("open(%s, ) failed, %s", pszPathOut, strerror(errno));
        close(fdIn);
        return false;
    }

    szDataBuf = malloc(DATA_BUF_SIZE);
    if (NULL == szDataBuf)
    {
        print_err("malloc() failed!");
        return false;
    }

    while (1)
    {
        INT32 slSizeRead = read(fdIn, szDataBuf, sizeof(szDataBuf));
        INT32 slSizeWrite;
        if (slSizeRead <= 0)
        {
            break;
        }

        slSizeWrite = write(fdOut, szDataBuf, slSizeRead);
        if (slSizeWrite < 0)
        {
            print_err("write(, , slSizeRead) failed, %s", slSizeRead, strerror(errno));
            break;
        }

        if (slSizeWrite != slSizeRead) /* verify wheter write all byte data successfully */
        {
            print_err(" write(, , %d) failed!", slSizeRead);
            break;
        }
    }

    close(fdIn);
    fsync(fdOut); /* causes all modified data and attributes to be moved to a permanent storage device */
    close(fdOut);

    ulFileSize_in = get_file_size(pszPathIn);
    ulFileSize_out = get_file_size(pszPathOut);
    if (ulFileSize_in == ulFileSize_out) /* verify again wheter write all byte data successfully */
    {
        free(szDataBuf);
        return true;
    }
    free(szDataBuf);
    return false;
}
0
kgbook

この解決策はより回避策ですが、クロスプラットフォームであるという利点があります。これは、最初のファイルの各文字を読み取り、2番目のファイルに書き込むことで構成されます。コードは次のとおりです(ファイルを開くエラー処理なし)。

void copyFile(char from[],char to[]) {
    FILE* copyFrom = fopen(from,"r");
    FILE* copyTo = fopen(to,"w");
    for (;;) {
        int caractereActuel = fgetc(copyFrom);
        if (caractereActuel != EOF) {
            fputc(caractereActuel,copyTo);
        }
        else {
            break;
        }
    }
    fclose(copyFrom);
    fclose(copyTo);
}
0
Donald Duck