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

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

50
задан Nikolai Fetissov 1 February 2010 в 11:08
поделиться

6 ответов

Нет необходимости либо звонить без портативных 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;
}
52
ответ дан 7 November 2019 в 10:46
поделиться

Это прямо вперед, чтобы использовать Fork / Execl для запуска CP для работы для вас. Это имеет преимущества по поводу системы в том, что она не склонна к атаке Бобби Таблицы, и вам не нужно дезинфицировать аргументы в той же степени. Кроме того, поскольку система () требует, чтобы вы собрали аргумент команд, вы вряд ли у вас есть проблема переполнения буфера из-за небрежной проверки SPRINTF ().

Преимущество для вызова CP напрямую вместо письма не нужно беспокоиться о элементах целевого пути, существующего в пункте назначения. Делать это в Roll-You-See Code, подвержена ошибкам и утомительно.

Я написал этот пример в ANSI C и устанавливал только для обработки ошибок Barest, кроме того, что это прямой код.

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 */
        {
        }
    }
}
20
ответ дан 7 November 2019 в 10:46
поделиться

Есть способ сделать это, не прибегая к вызову системы , вам нужно включить обертку что-то вроде этого:

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

Вышеприведенный пример (проверка ошибок пропущена!) использует open, close и sendfile.

Edit: As caf has pointed out a race condition may occur between the open and stat so I thought I'd make this a little robust...Имейте в виду, что механизм блокировки варьируется от платформы к платформе...под Linux, этого механизма блокировки с lockf было бы достаточно. Если вы хотите сделать его портативным, используйте макросы #ifdef, чтобы различать различные платформы/компиляторы... Спасибо caf за то, что заметили это... Здесь есть ссылка на сайт, который выдал "универсальную процедуру блокировки" .

3
ответ дан 7 November 2019 в 10:46
поделиться
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);

system( cmd);

Добавьте некоторые проверки ошибок ...

В противном случае откройте оба и цикл на чтение / запись, но, вероятно, не то, что вы хотите.

...

Обновление для устранения действительных проблем безопасности:

, а не с использованием «Система ()», выполните вилку / ожидание, и вызовите EXECV () или EXECL () у ребенка.

execl( "/bin/cp", "-p", old, new);
3
ответ дан 7 November 2019 в 10:46
поделиться

В API нет выпеченных эквивалентных копировальных файлов в API. Но SendFile можно использовать для копирования файла в режиме ядра, который является быстрее и лучшим решением (для многочисленных причин), чем открывать файл, зацикливаться через него, чтобы прочитать в буфер, и запись вывода на другой файл.

Обновление:

в виде ядра 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;
}

Редактировать : заменил открытие пункта назначения с помощью вызова на creal () , как мы хотим, чтобы флаг o_trunc . Смотрите комментарий ниже.

21
ответ дан 7 November 2019 в 10:46
поделиться

Один из вариантов - вы можете использовать system () для выполнения cp . Это просто повторно использует команду cp (1) для выполнения работы. Если вам нужно только создать еще одну ссылку на файл, это можно сделать с помощью link () или symlink () .

2
ответ дан 7 November 2019 в 10:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: