Как я могу скопировать файл в Unix, используя C? - PullRequest
48 голосов
/ 02 февраля 2010

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

Ответы [ 8 ]

51 голосов
/ 02 февраля 2010

Нет необходимости вызывать непереносимые 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;
}
21 голосов
/ 02 февраля 2010

Нетрудно использовать fork / execl для запуска cp, чтобы сделать всю работу за вас. Это имеет преимущества перед системой в том, что она не подвержена атаке Бобби Таблиц, и вам не нужно очищать аргументы в той же степени. Кроме того, поскольку system () требует от вас объединения аргумента команды, у вас вряд ли возникнет проблема переполнения буфера из-за неаккуратной проверки 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 */
        {
        }
    }
}
20 голосов
/ 02 февраля 2010

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

Обновление:

Начиная с версии ядра ядра 2.6.33, ограничение, требующее, чтобы вывод sendfile был сокетом, было отменено, и исходный код работал бы как в Linux, так и - однако, для OS X 10.9 Mavericks, sendfile в OS X теперь требуется, чтобы вывод был сокетом, а код не работал!

Следующий фрагмент кода должен работать на большинстве OS X (по состоянию на 10.5), (Free) 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;
}

РЕДАКТИРОВАТЬ : Заменить открытие пункта назначения вызовом на creat(), так как мы хотим, чтобы был указан флаг O_TRUNC. См. Комментарий ниже.

5 голосов
/ 11 сентября 2014

Другой вариант функции копирования с использованием обычных вызовов 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;
}

РЕДАКТИРОВАТЬ : Исправлена ​​ошибка создания файла. См. Комментарий в /2128006/kak-ya-mogu-skopirovat-fail-v-unix-ispolzuya-c#2128016 ответ.

3 голосов
/ 02 февраля 2010

Есть способ сделать это, не прибегая к вызову 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 */
}

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

Редактировать: Как caf указал состояние гонки может возникнуть между open и stat, поэтому я думал, что я бы сделал это немного более надежным ... Имейте в виду, что механизм блокировки варьируется от платформы к платформе ... под Linux этого механизма блокировки с lockf было бы достаточно. Если вы хотите сделать это переносимым, используйте макросы #ifdef, чтобы различать разные платформы / компиляторы ... Спасибо cafе за обнаружение этого ... Здесь есть ссылка на сайт, который привел к "универсальным процедурам блокировки" здесь .

3 голосов
/ 02 февраля 2010
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);

system( cmd);

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

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

...

ОБНОВЛЕНИЕ для решения действительных проблем безопасности:

Вместо использования "system ()" выполните fork / wait и вызовите execv () или execl () для дочернего элемента.

execl( "/bin/cp", "-p", old, new);
2 голосов
/ 02 февраля 2010

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

0 голосов
/ 15 сентября 2018
#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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...