Я ищу эквивалент Unix Win32 CopyFile, я не хочу изобретать колесо, написав свою собственную версию.
Как скопировать файл в Unix с помощью C?
Ответ 1
Нет необходимости либо вызывать непереносимые 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;
}
Ответ 2
Прямо вперед, чтобы использовать 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 */
{
}
}
}
Ответ 3
В API-интерфейсах нет функции CopyFile, эквивалентной выпечке. Но sendfile можно использовать для копирования файла в режиме ядра, который является более быстрым и лучшим решением (по многочисленным причинам), чем открытие файла, зацикливая его на чтение в буфер и записывая вывод в другой файл.
Update:
В версии ядра Linux 2.6.33 ограничение, требующее вывода сокета sendfile
, было снято, и исходный код работал как на Linux, так и на OS X 10.9 Mavericks, sendfile
на OS X теперь требуется, чтобы выход был сокетом, а код не работает!
Следующий фрагмент кода должен работать с большинством OS X (с 10.5), (бесплатно) BSD и Linux (начиная с 2.6.33). Реализация является "нулевой копией" для всех платформ, что означает, что все это выполняется в kernelspace и не происходит копирование буферов или данных в пользовательском пространстве и из него. В значительной степени лучшая производительность, которую вы можете получить.
#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
. См. Комментарий ниже.
Ответ 4
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);
system( cmd);
Добавить некоторые проверки ошибок...
В противном случае откройте оба и цикл на чтение/запись, но, вероятно, не то, что вы хотите.
...
UPDATE для решения проблем безопасности:
Вместо того, чтобы использовать "system()", выполните fork/wait и вызовите execv() или execl() в дочернем элементе.
execl( "/bin/cp", "-p", old, new);
Ответ 5
Есть способ сделать это, не прибегая к вызову 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 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 */
}
В приведенном выше примере (проверка ошибок опущена!) используются open
, close
и sendfile
.
Изменить:. Как caf указал, что между open
и stat
может возникнуть состояние гонки, поэтому я думал, что сделаю это немного более надежным... Имейте в виду, что механизм блокировки варьируется от платформы к платформе... в Linux этот механизм блокировки с lockf
будет достаточным. Если вы хотите сделать этот перенос, используйте макросы #ifdef
, чтобы различать разные платформы/компиляторы... Спасибо caf за то, что вы заметили это... Существует ссылка на сайт, который дал "универсальные процедуры блокировки" здесь.
Ответ 6
Другой вариант функции копирования с использованием обычных вызовов POSIX и без какого-либо цикла. Код, вдохновленный вариантом буферической копии ответа кафе.
Предупреждение: использование 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: Исправлена ошибка создания файла. См. Комментарий в fooobar.com/questions/141627/....
Ответ 7
Один из вариантов заключается в том, что вы можете использовать system()
для выполнения cp
. Это просто повторяет команду cp(1)
для выполнения этой работы. Если вам нужна только другая ссылка на файл, это можно сделать с помощью link()
или symlink()
.
Ответ 8
Это решение скорее обходное решение, но оно имеет преимущество быть кросс-платформенным. Он состоит в чтении каждого символа первого файла и записи его во втором файле. Вот код (без обработки ошибок открытия файла):
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);
}