C语言 如何使用 C 在 Unix 上复制文件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2180079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I copy a file on Unix using C?
提问by Motti
回答by caf
There is no need to either call non-portable APIs like sendfile, or shell out to external utilities. The same method that worked back in the 70s still works now:
无需调用非可移植的 API,如sendfile,或外壳到外部实用程序。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;
}
回答by Mahmoud Al-Qudsi
There is no baked-in equivalent CopyFile function in the APIs. But sendfilecan be used to copy a file in kernel mode which is a faster and better solution (for numerous reasons) than opening a file, looping over it to read into a buffer, and writing the output to another file.
API 中没有内置的等效 CopyFile 函数。但是sendfile可用于在内核模式下复制文件,这是比打开文件、循环读取文件并将输出写入另一个文件更快更好的解决方案(出于多种原因)。
Update:
更新:
As of Linux kernel version 2.6.33, the limitation requiring the output of sendfileto be a socket was lifted and the original code would work on both Linux and — however, as of OS X 10.9 Mavericks, sendfileon OS X now requires the output to be a socket and the code won't work!
从 Linux 内核版本 2.6.33 开始,要求输出为sendfile套接字的限制被取消,原始代码可以在 Linux 上运行,但是,从 OS X 10.9 Mavericks 开始,sendfile在 OS X 上现在要求输出为一个套接字,代码将不起作用!
The following code snippet should work on the most OS X (as of 10.5), (Free)BSD, and Linux (as of 2.6.33). The implementation is "zero-copy" for all platforms, meaning all of it is done in kernelspace and there is no copying of buffers or data in and out of userspace. Pretty much the best performance you can get.
以下代码片段应该适用于大多数 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;
}
EDIT: Replaced the opening of the destination with the call to creat()as we want the flag O_TRUNCto be specified. See comment below.
编辑:将目的地的开头替换creat()为我们希望O_TRUNC指定标志的调用。请参阅下面的评论。
回答by plinth
It's straight forward to use fork/execl to run cp to do the work for you. This has advantages over system in that it is not prone to a Bobby Tables attack and you don't need to sanitize the arguments to the same degree. Further, since system() requires you to cobble together the command argument, you are not likely to have a buffer overflow issue due to sloppy sprintf() checking.
直接使用 fork/execl 运行 cp 来为您完成工作。这比系统具有优势,因为它不容易受到 Bobby Tables 攻击,并且您不需要以相同的程度清理参数。此外,由于 system() 要求您拼凑命令参数,因此您不太可能由于草率的 sprintf() 检查而遇到缓冲区溢出问题。
The advantage to calling cp directly instead of writing it is not having to worry about elements of the target path existing in the destination. Doing that in roll-you-own code is error-prone and tedious.
直接调用 cp 而不是编写它的优点是不必担心目标路径中存在的元素。在自己的代码中这样做容易出错且乏味。
I wrote this example in ANSI C and only stubbed out the barest error handling, other than that it's straight forward code.
我用 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 */
{
}
}
}
回答by Patrick Schlüter
Another variant of the copy function using normal POSIX calls and without any loop. Code inspired from the buffer copy variant of the answer of caf.
Warning: Using mmapcan easily fail on 32 bit systems, on 64 bit system the danger is less likely.
使用普通 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: Corrected the file creation bug. See comment in http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/2180157#2180157answer.
编辑:更正了文件创建错误。请参阅http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/2180157#2180157答案中的评论。
回答by Roboprog
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);
system( cmd);
Add some error checks...
添加一些错误检查...
Otherwise, open both and loop on read/write, but probably not what you want.
否则,打开两者并循环读/写,但可能不是您想要的。
...
...
UPDATE to address valid security concerns:
更新以解决有效的安全问题:
Rather than using "system()", do a fork/wait, and call execv() or execl() in the child.
与其使用“system()”,不如执行 fork/wait,然后在子进程中调用 execv() 或 execl()。
execl( "/bin/cp", "-p", old, new);
回答by t0mm13b
There is a way to do this, without resorting to the systemcall, you need to incorporate a wrapper something like this:
有一种方法可以做到这一点,而无需求助于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 */
}
The above sample (error checking is omitted!) employs open, closeand sendfile.
上面的示例(省略了错误检查!)使用open,close和sendfile。
Edit:As cafhas pointed out a race conditioncan occur between the openand statso I thought I'd make this a bit more robust...Keep in mind that the locking mechanism varies from platform to platform...under Linux, this locking mechanism with lockfwould suffice. If you want to make this portable, use the #ifdefmacros to distinguish between different platforms/compilers...Thanks caf for spotting this...There is a link to a site that yielded "universal locking routines" here.
编辑:正如caf指出的那样可能会发生竞争条件open,stat所以我想我会让它更健壮一点......请记住,锁定机制因平台而异......在 Linux 下,这种锁定有机制lockf就够了。如果你想使这个便携,使用#ifdef宏为察觉这种不同的平台/编译器...感谢CAF区分...有到取得“通用锁定例程”一个网站的链接在这里。
回答by ConcernedOfTunbridgeWells
One option is that you could use system()to execute cp. This just re-uses the cp(1)command to do the work. If you only need to make another link the the file, this can be done with link()or symlink().
一种选择是您可以system()用来执行cp. 这只是重新使用cp(1)命令来完成工作。如果您只需要为文件创建另一个链接,则可以使用link()或来完成symlink()。
回答by kgbook
#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;
}

