Подтвердить что ты не робот

Как скопировать файл в Unix с помощью C?

Я ищу эквивалент Unix Win32 CopyFile, я не хочу изобретать колесо, написав свою собственную версию.

4b9b3361

Ответ 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);
}