如何在Unix上使用C复制文件?

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

不需要调用诸如sendfile类的非可移植API,也不需要向外部实用程序发送shell。 在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; } 

使用fork / execl运行cp来为您完成工作是非常简单的。 这比系统有优势,因为它不容易受到鲍比表的攻击,而且你也不需要以相同的程度去清理参数。 此外,由于system()要求你凑齐命令参数,所以你不可能由于sprintf()检查的松懈而导致缓冲区溢出问题。

直接调用cp而不是写它的好处是不必担心目标path中存在的元素。 这样做在你自己的代码是容易出错和乏味的。

我在ANSI C中编写了这个例子,只是简单地说明了最简单的error handling,除此之外它是直接的代码。

 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 */ { } } } 

API中没有相应的CopyFile函数。 但sendfile可以用来以内核模式复制一个文件,这是一个更快更好的解决scheme(出于许多原因),而不是打开一个文件,循环读取一个缓冲区,并将输出写入另一个文件。

更新:

从Linux内核版本2.6.33开始,要求输出sendfile作为套接字的限制被解除了,原来的代码可以在Linux上运行 – 但是,从OS X 10.9 Mavericks开始,OS X上的sendfile现在需要输出成为一个套接字和代码将无法正常工作!

下面的代码片段应该适用于大多数OS X(自10.5),(免费)BSD和Linux(自2.6.33起)。 所有平台的实现都是“零拷贝”,这意味着所有的操作都是在内核空间中完成的,并且不会将缓冲区或数据拷入和拷出用户空间。 非常好,你可以得到最好的performance。

 #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 (ie 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指定。 见下面的评论。

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

添加一些错误检查…

否则,打开并循环读/写,但可能不是你想要的。

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

而不是使用“system()”,做一个fork / wait,然后在subprocess中调用execv()或execl()。

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

有一种方法可以做到这一点,而不诉诸于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和发送文件。

编辑:正如caf指出,可以发生在openstat之间的竞争条件 ,所以我想我会使这一点更强大…请记住,locking机制因平台而异…在Linux下,这个lockflocking机制就足够了。 如果你想使这个便携式,使用#ifdefmacros来区分不同的平台/编译器…感谢caf发现这个…有一个网站的链接,产生“通用locking例程” 在这里 。

使用普通的POSIX调用和没有任何循环的复制function的另一种变体。 代码灵感来自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; } 

编辑 :更正了文件创build错误。 请参阅http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/21​​80157#2180157答案中的评论。;

一种select是你可以使用system()来执行cp 。 这只是重新使用cp(1)命令来完成这项工作。 如果你只需要build立另一个链接文件,可以用link()或者symlink()来完成。

这个解决scheme更多的是解决方法,但它具有跨平台的优势。 它包括读取第一个文件的每个字符并将其写入第二个文件。 这里是代码(没有文件打开error handling):

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