【问题标题】:How can I copy a file on Unix using C?如何使用 C 在 Unix 上复制文件?
【发布时间】:2011-01-11 21:53:31
【问题描述】:

我正在寻找与 Win32 的 CopyFile 等效的 Unix,我不想通过编写自己的版本来重新发明轮子。

【问题讨论】:

  • 为了不重新发明轮子编译 GNU coreutils,AFAIK 它有一个用于在其构建树中复制文件的静态库,供cp 和其他人使用。它支持稀疏和 btrfs 牛

标签: c unix copy


【解决方案1】:

好问题。与另一个好问题相关:

In C on linux how would you implement cp

对于 cp 的“最简单”实现有两种方法。一种方法使用某种文件复制系统调用函数——我们得到的最接近 Unix cp 命令的 C 函数版本的东西。另一种方法使用缓冲区和读/写系统调用函数,可以直接使用,也可以使用 FILE 包装器。

很可能只发生在内核拥有的内存中的文件复制系统调用比发生在内核和用户拥有的内存中的系统调用要快,尤其是在网络文件系统设置中(在机器之间复制)。但这需要测试(例如使用 Unix 命令时间),并且取决于编译和执行代码的硬件。

操作系统没有标准 Unix 库的人也可能想要使用您的代码。然后你想使用缓冲区读/写版本,因为它只依赖于 (和朋友)

这是一个使用 unix 标准库 <unistd.h> 中的函数 copy_file_range 将源文件复制到(可能不存在的)目标文件的示例。复制发生在内核空间中。

/* copy.c
 *
 * Defines function copy:
 *
 * Copy source file to destination file on the same filesystem (possibly NFS).
 * If the destination file does not exist, it is created. If the destination
 * file does exist, the old data is truncated to zero and replaced by the 
 * source data. The copy takes place in the kernel space.
 *
 * Compile with:
 *
 * gcc copy.c -o copy -Wall -g
 */

#define _GNU_SOURCE 
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

/* On versions of glibc < 2.27, need to use syscall.
 * 
 * To determine glibc version used by gcc, compute an integer representing the
 * version. The strides are chosen to allow enough space for two-digit 
 * minor version and patch level.
 *
 */
#define GCC_VERSION (__GNUC__*10000 + __GNUC_MINOR__*100 + __gnuc_patchlevel__)
#if GCC_VERSION < 22700
static loff_t copy_file_range(int in, loff_t* off_in, int out, 
  loff_t* off_out, size_t s, unsigned int flags)
{
  return syscall(__NR_copy_file_range, in, off_in, out, off_out, s,
    flags);
}
#endif

/* The copy function.
 */
int copy(const char* src, const char* dst){
  int in, out;
  struct stat stat;
  loff_t s, n;
  if(0>(in = open(src, O_RDONLY))){
    perror("open(src, ...)");
    exit(EXIT_FAILURE);
  }
  if(fstat(in, &stat)){
    perror("fstat(in, ...)");
    exit(EXIT_FAILURE);
  }
  s = stat.st_size; 
  if(0>(out = open(dst, O_CREAT|O_WRONLY|O_TRUNC, 0644))){
    perror("open(dst, ...)");
    exit(EXIT_FAILURE);
  }
  do{
    if(1>(n = copy_file_range(in, NULL, out, NULL, s, 0))){
      perror("copy_file_range(...)");
      exit(EXIT_FAILURE);
    }
    s-=n;
  }while(0<s && 0<n);
  close(in);
  close(out);
  return EXIT_SUCCESS;
}

/* Test it out.
 *
 * BASH:
 *
 * gcc copy.c -o copy -Wall -g
 * echo 'Hello, world!' > src.txt
 * ./copy src.txt dst.txt
 * [ -z "$(diff src.txt dst.txt)" ]
 *
 */

int main(int argc, char* argv[argc]){
  if(argc!=3){
    printf("Usage: %s <SOURCE> <DESTINATION>", argv[0]);
    exit(EXIT_FAILURE);
  }
  copy(argv[1], argv[2]);
  return EXIT_SUCCESS;
}

它基于我的 Ubuntu 20.x Linux 发行版的 copy_file_range 手册页中的示例。检查你的手册页:

> man copy_file_range

然后点击jEnter,直到您进入示例部分。或输入/example 进行搜索。

/ 仅限

这是一个仅使用stdlib/stdio 的示例。缺点是它在用户空间中使用了一个中间缓冲区。

/* copy.c
 *
 * Compile with:
 * 
 * gcc copy.c -o copy -Wall -g
 *
 * Defines function copy:
 *
 * Copy a source file to a destination file. If the destination file already
 * exists, this clobbers it. If the destination file does not exist, it is
 * created. 
 *
 * Uses a buffer in user-space, so may not perform as well as 
 * copy_file_range, which copies in kernel-space.
 *
 */

#include <stdlib.h>
#include <stdio.h>

#define BUF_SIZE 65536 //2^16

int copy(const char* in_path, const char* out_path){
  size_t n;
  FILE* in=NULL, * out=NULL;
  char* buf = calloc(BUF_SIZE, 1);
  if((in = fopen(in_path, "rb")) && (out = fopen(out_path, "wb")))
    while((n = fread(buf, 1, BUF_SIZE, in)) && fwrite(buf, 1, n, out));
  free(buf);
  if(in) fclose(in);
  if(out) fclose(out);
  return EXIT_SUCCESS;
}

/* Test it out.
 *
 * BASH:
 *
 * gcc copy.c -o copy -Wall -g
 * echo 'Hello, world!' > src.txt
 * ./copy src.txt dst.txt
 * [ -z "$(diff src.txt dst.txt)" ]
 *
 */
int main(int argc, char* argv[argc]){
  if(argc!=3){
    printf("Usage: %s <SOURCE> <DESTINATION>\n", argv[0]);
    exit(EXIT_FAILURE);
  }
  return copy(argv[1], argv[2]);
}

【讨论】:

    【解决方案2】:

    我看到还没有人提到 copy_file_range,至少在 Linux 上受支持 and FreeBSD。这个的优点是它明确记录了利用 CoW 技术(如 reflinks)的能力。引用:

    copy_file_range() 为文件系统提供了实现“复制加速”技术的机会,例如使用 reflinks(即,两个或多个 inode 共享指向相同的写时复制磁盘块的指针) em> 或服务器端复制(在 NFS 的情况下)

    FWIW,我不确定老 sendfile 是否能够做到这一点。我发现的少数提及声称它没有。从这个意义上说,copy_file_range 优于 sendfile

    以下是使用调用的示例(从手册中逐字复制)。我还检查了在使用此代码在 BTRFS 文件系统中复制 bash 二进制文件后,该副本被重新链接到原始 (我通过在文件上调用 duperemove 并看到 Skipping - extents are already deduped. 消息来做到这一点)。

    #define _GNU_SOURCE
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    int
    main(int argc, char **argv)
    {
        int fd_in, fd_out;
        struct stat stat;
        off64_t len, ret;
    
        if (argc != 3) {
            fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        fd_in = open(argv[1], O_RDONLY);
        if (fd_in == -1) {
            perror("open (argv[1])");
            exit(EXIT_FAILURE);
        }
    
        if (fstat(fd_in, &stat) == -1) {
            perror("fstat");
            exit(EXIT_FAILURE);
        }
    
        len = stat.st_size;
    
        fd_out = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd_out == -1) {
            perror("open (argv[2])");
            exit(EXIT_FAILURE);
        }
    
        do {
            ret = copy_file_range(fd_in, NULL, fd_out, NULL, len, 0);
            if (ret == -1) {
                perror("copy_file_range");
                exit(EXIT_FAILURE);
            }
    
            len -= ret;
        } while (len > 0 && ret > 0);
    
        close(fd_in);
        close(fd_out);
        exit(EXIT_SUCCESS);
    }
    

    【讨论】:

      【解决方案3】:

      很简单:

      #define BUF_SIZE 65536
      
      int cp(const char *from, const char*to){
      FILE *src, *dst;
      size_t in, out;
      char *buf = (char*) malloc(BUF_SIZE* sizeof(char));
      src = fopen(from, "rb");
      if (NULL == src) exit(2);
      dst = fopen(to, "wb");
      if (dst < 0) exit(3);
      while (1) {
          in = fread(buf, sizeof(char), BUF_SIZE, src);
          if (0 == in) break;
          out = fwrite(buf, sizeof(char), in, dst);
          if (0 == out) break;
      }
      fclose(src);
      fclose(dst);
      }
      

      适用于 windows 和 linux。

      【讨论】:

        【解决方案4】:

        逐字节复制文件确实有效,但在现代 UNIX 上是缓慢且浪费的。现代 UNIX 在文件系统中内置了“写时复制”支持:系统调用创建一个指向磁盘上现有字节的新目录条目,并且在修改其中一个副本之前不会触及磁盘上的文件内容字节,此时只有更改的块被写入磁盘。这允许不使用额外文件块的近乎即时的文件复制,而不管文件大小。例如,这里有一些关于how this works in xfs的详细信息。

        在 linux 上,使用 the FICLONE ioctl as coreutils cp now does by default

         #ifdef FICLONE
           return ioctl (dest_fd, FICLONE, src_fd);
         #else
           errno = ENOTSUP;
           return -1;
         #endif
        

        在 macOS 上,使用 clonefile(2) 在 APFS 卷上进行即时复制。这就是 Apple 的 cp -c 使用的。文档并不完全清楚,但copyfile(3) with COPYFILE_CLONE 很可能也使用它。如果您希望我对此进行测试,请发表评论。

        如果不支持这些写时复制操作——无论是操作系统太旧、底层文件系统不支持它,还是因为你在不同的文件系统之间复制文件——你确实需要回退到尝试sendfile,或者作为最后的手段,逐字节复制。但是为了给大家节省很多时间和磁盘空间,请先试试FICLONEclonefile(2)

        【讨论】:

          【解决方案5】:

          一种选择是您可以使用system() 来执行cp。这只是重新使用cp(1) 命令来完成这项工作。如果您只需要创建另一个文件链接,可以使用link()symlink() 完成。

          【讨论】:

          • 注意 system() 是一个安全漏洞。
          • 真的吗?你会在生产代码中使用它吗?我想不出一个很好的理由不这样做,但我觉得它不是一个干净的解决方案。
          • 如果你指定到 /bin/cp 的路径,你就相对安全了,除非攻击者设法破坏系统,以至于他们可以修改 /bin 中的任意系统 shell 实用程序。如果他们对系统造成了这种程度的破坏,那么你就会遇到更大的问题。
          • 使用系统运行命令在 unix-land 中相当普遍。通过适当的卫生,它可以相当安全和坚固。毕竟,这些命令被设计成以这种方式使用。
          • 如果用户创建一个像“somefile;rm /bin/*”这样的文件名会发生什么? system() 使用 sh -c 执行命令,因此整个字符串的文本由 shell 执行,这意味着在将分号作为命令执行后您会得到任何东西 - 如果您的代码也在运行 setuid,那么会很臭。这与 Bobby Tables (xkcd.com/327) 没有什么不同。对于完全清理 system() 所带来的麻烦,您可以改为直接在 /bin/cp 上使用正确的参数执行 fork/exec 对。
          【解决方案6】:
          #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;
          }
          

          【讨论】:

            【解决方案7】:

            使用普通 POSIX 调用且没有任何循环的复制函数的另一个变体。代码灵感来自 caf 答案的缓冲区复制变体。 警告:在 32 位系统上使用 mmap 很容易失败,在 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/2180157#2180157 答案中的评论。

            【讨论】:

            • stackoverflow.com/questions/2180079/… 相同的错误。如果目标已存在且大于源,则文件副本只会部分覆盖目标,不会截断生成的文件;
            • (我意识到这是一个老问题,但是......)当被映射的文件的大小与可用内存和交换文件的大小相比非常大时,mmap 会发生什么?会在内存不足/交换情况下挂起系统吗?
            • 将文件映射到进程的地址范围本身并不占用任何内存。就好像您说您的文件现在是交换空间的一部分。这意味着当您访问映射文件中的地址时,它将首先生成页面错误,因为内存中没有任何内容。然后操作系统从磁盘加载该地址的相应页面并将控制权恢复到进程。如果没有可用内存,那么操作系统将简单地从任何其他进程中释放一些其他映射页面;在优先清洁页面(即不需要写入磁盘)以及脏页面中。 =>
            • 当对映射页面的访问模式超过系统中的物理内存量并且它必须一直读取和写入页面时,就会发生交换。 mmap 可以看作无非只是增加了系统交换区域。带有选项 MAP_SHARED 的 mmap 也可以被视为使进程可以访问文件缓存的一种方式。
            • 所以如果你mmap一个大文件,然后访问很多,并且你访问的文件量大于你的真实内存,操作系统就会开始分页其他进程。如果这种情况发生太多,操作系统将开始在交换活动上颠簸。我的观点是,对于相对于内存 + 交换较大的文件,您必须考虑正在访问的 mmap 数据的大小,以免造成问题
            【解决方案8】:

            API 中没有内置的等效 CopyFile 函数。但是sendfile 可用于在内核模式下复制文件,这比打开文件、循环读取文件并将输出写入另一个文件更快更好(出于多种原因)。

            更新:

            从 Linux 内核版本 2.6.33 开始,要求将 sendfile 的输出作为套接字的限制被取消,原始代码可以在 Linux 上运行,但是,从 OS X 10.9 Mavericks 开始,sendfile在 OS X 上现在要求输出是一个套接字,并且代码将不起作用!

            以下代码 sn-p 应该适用于大多数 OS X(截至 10.5)、(免费)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;
            }
            

            编辑:将目标的打开替换为对creat() 的调用,因为我们希望指定标志O_TRUNC。请参阅下面的评论。

            【讨论】:

            • 根据手册页,sendfile 的输出参数必须是套接字。你确定这行得通吗?
            • 对于 Linux,Jay Conrod 是对的 - sendfileout_fd 可能是 2.4 内核中的常规文件,但它现在必须支持 sendpage 内部内核 API(这实际上意味着管道或插座)。 sendpage 在不同的 UNIX 上以不同的方式实现 - 它没有标准语义。
            • Linux 下的原型与 OSX 不同,因此当我看到您的实现并看到 sendfile 的额外参数时,您会认为(我也认为)...它取决于平台- 值得记住的事情!
            • fyi - 使用 if (PathsMatch(source, destination)) return 1; 可以节省大量工作; /* 其中 PathsMatch 是针对区域设置的适当路径比较例程 */,否则我想第二次打开会失败。
            • +1 man sendfile 表示从 2.6.33 开始,再次支持此功能。 sendfile() 优于 CopyFile(),因为它允许偏移。这对于从文件中剥离标题信息很有用。
            【解决方案9】:

            有一种方法可以做到这一点,无需使用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 */
            }
            

            以上示例(省略了错误检查!)使用了openclosesendfile

            编辑:正如 caf 指出的那样,open 和 @ 之间可能会发生 竞态条件 987654328@ 所以我想我会让它更健壮一点...请记住,锁定机制因平台而异...在 Linux 下,lockf 的锁定机制就足够了。如果你想让它可移植,请使用#ifdef 宏来区分不同的平台/编译器...感谢 caf 发现这一点...有一个指向产生“通用锁定例程”here 的站点的链接。

            【讨论】:

            • 我不是 100% 确定 sendfile 原型,我想我弄错了一个参数...请记住这一点... :)
            • 你有一个竞争条件 - 你以fdSource 打开的文件和你以stat()ed 打开的文件不一定相同。
            • @caf:您能否提供更多详细信息,因为我正在查看它以及如何存在竞争条件?我会相应地修改答案..谢谢你让我知道...
            • tommbieb75:简单 - 在 open() 调用和 stat() 调用之间,其他人可以重命名文件并在该名称下放置一个不同的文件 - 因此您将从第一个文件,但使用第二个文件的长度。
            • @caf: Holy moly....我为什么没想到...很好发现...锁应该对源文件起作用...很好发现那……比赛条件……好吧,我从来没有……正如《大都灵》中的克林特·伊斯特伍德所说的那样,“整个星期五都是 JC……”
            【解决方案10】:
            sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);
            
            system( cmd);
            

            添加一些错误检查...

            否则,同时打开并循环读取/写入,但可能不是您想要的。

            ...

            更新以解决有效的安全问题:

            与其使用“system()”,不如执行 fork/wait,并在子进程中调用 execv() 或 execl()。

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

            【讨论】:

            • 这不适用于名称中包含空格(或引号、反斜杠、美元符号等)的文件。我经常在文件名中使用空格。
            • 哎哟。这是正确的。在 sprintf() 中的文件名周围添加反斜杠单引号。
            • 好的,这是瑞士奶酪(请参阅其他地方 cmets 中的有效安全问题),但如果您有一个相对受控的环境,它可能会有一些用处。
            • 如果您没有正确处理oldnew 值中的单引号字符,则存在shell 代码注入漏洞。多一点努力使用 fork 并自己执行 exec 可以避免所有这些引用问题。
            • 是的,在很多情况下,简单明显和错误。这就是为什么我对一些更复杂的例子投了赞成票。
            【解决方案11】:

            直接使用 fork/execl 运行 cp 为您完成工作。这与系统相比具有优势,因为它不容易受到 Bobby Tables 攻击,并且您不需要对参数进行相同程度的清理。此外,由于 system() 要求您将命令参数拼凑在一起,因此您不太可能因为 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 */
                    {
                    }
                }
            }
            

            【讨论】:

            • +1 表示另一个冗长、详细的记录。真正让您欣赏 perl 中 system() 的“向量”/列表形式。唔。也许一个带有 argv 数组的系统函数会很好?!?
            • ...毕竟它是在 17 年前在 glibc 中实现的,并且在您编写答案之前 10 年成为标准功能..
            【解决方案12】:

            不需要调用像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;
            }
            

            【讨论】:

            • @Caf: OMG....goto.... :) 你的代码比我的更理智... ;) 带有读/写的旧循环是最便携的...来自我的 +1...
            • 我发现goto 的受控使用对于将错误处理路径整合到一个位置很有用。
            • 不可用于一般用途。文件的副本不仅仅是数据流。稀疏文件或扩展属性怎么样?这就是为什么 Windows API 比 Linux 更丑的原因
            • 您在write() 循环中处理EINTR,但不在read() 循环中。
            • @Lothar Unix 文件在概念上只是一个字节序列。权限、ACL 等元数据与数据的实际复制正交处理。正如他们应该的那样。特定于应用程序的文件格式是应用程序的问题。正如他们应该的那样。
            猜你喜欢
            • 2018-09-22
            • 1970-01-01
            • 2011-08-07
            • 2017-01-28
            • 2015-08-26
            • 1970-01-01
            • 2014-05-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多