web-dev-qa-db-fra.com

Comment copier un fichier sous Unix avec C?

Je recherche l'équivalent Unix de Win32's CopyFile , je ne veux pas réinventer la roue en écrivant ma propre version.

45
Motti

Il n'est pas nécessaire d'appeler des API non portables telles que sendfile, ni de Shell vers des utilitaires externes. La même méthode qui a fonctionné dans les années 70 fonctionne toujours maintenant:

#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;
}
46
caf

Il n'y a pas de fonction CopyFile incorporée équivalente dans les API. Mais sendfile peut être utilisé pour copier un fichier en mode noyau, ce qui constitue une solution plus rapide et meilleure (pour de nombreuses raisons) que d'ouvrir un fichier, de le lire en boucle dans un tampon et d'écrire le résultat dans un autre fichier.

Mettre à jour:

À partir de la version 2.6.33 du noyau Linux, la limitation exigeant que la sortie de sendfile soit un socket a été levée et le code original fonctionnait à la fois sous Linux et - à partir de OS X 10.9 Mavericks, sendfile sous OS X nécessite maintenant être une socket et le code ne fonctionnera pas!

L'extrait de code suivant devrait fonctionner sous OS X (version 10.5), BSD (libre) et Linux (version 2.6.33). L'implémentation est "zéro copie" pour toutes les plates-formes, ce qui signifie que tout est fait dans l'espace noyau et qu'il n'y a pas de copie des tampons ou des données dans et en dehors de l'espace utilisateur. À peu près la meilleure performance que vous pouvez obtenir.

#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: remplacement de l'ouverture de la destination par l'appel à creat() car nous souhaitons que l'indicateur O_TRUNC soit spécifié. Voir le commentaire ci-dessous.

20
Mahmoud Al-Qudsi

Il est facile d'utiliser fork/execl pour exécuter cp afin de faire le travail à votre place. Cela présente des avantages par rapport au système dans la mesure où il n’est pas sujet à une attaque par Bobby Tables et vous n’avez pas besoin de nettoyer les arguments de la même manière. De plus, puisque system () nécessite que vous bricoliez l'argument de la commande, il est peu probable que vous rencontriez un problème de débordement de mémoire tampon du fait d'une vérification sloppy sprintf ().

L'avantage d'appeler cp directement au lieu de l'écrire est de ne pas s'inquiéter des éléments du chemin cible existant dans la destination. Faire cela dans du code personnel est une source d'erreurs et fastidieuse.

J'ai écrit cet exemple en ANSI C et je n'ai fait qu'effleurer le traitement des erreurs, à part le fait qu'il s'agisse d'un code simple.

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
plinth
sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new);

system( cmd);

Ajouter des vérifications d'erreur ...

Sinon, ouvrez les deux et bouclez en lecture/écriture, mais probablement pas ce que vous voulez.

...

UPDATE pour répondre aux préoccupations de sécurité valables:

Plutôt que d'utiliser "system ()", faites un fork/wait et appelez execv () ou execl () dans l'enfant.

execl( "/bin/cp", "-p", old, new);
4
Roboprog

Une autre variante de la fonction de copie utilisant les appels POSIX normaux et sans aucune boucle. Code inspiré de la variante de copie tampon de la réponse de caf ..__ Avertissement: L'utilisation de mmap peut facilement échouer sur les systèmes 32 bits, sur les systèmes 64 bits, le danger est moins probable.

#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: Correction du bogue de création de fichier. Voir le commentaire dans http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/2180157#2180157 answer.

4
Patrick Schlüter

Une option consiste à utiliser system() pour exécuter cp. Cela ne fait que réutiliser la commande cp(1) pour effectuer le travail. Si vous devez uniquement créer un autre lien dans le fichier, vous pouvez le faire avec link() ou symlink().

Il existe un moyen de le faire, sans recourir à l'appel system, vous devez incorporer un wrapper qui ressemble à ceci:

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

L'exemple ci-dessus (la vérification d'erreur est omise!) Utilise open, close et sendfile.

Edit: Comme caf a indiqué qu'une condition de concurrence peut se produire entre les variables open et stat, j'ai donc pensé que cela rendrait le système un peu plus robuste. .. Gardez à l'esprit que le mécanisme de verrouillage varie d'une plate-forme à l'autre ... sous Linux, ce mécanisme de verrouillage avec lockf suffirait. Si vous voulez rendre ce portable, utilisez les macros #ifdef pour distinguer les différentes plates-formes/compilateurs ... Merci caf pour avoir repéré cela ... Il existe un lien vers un site qui a généré des "routines de verrouillage universelles" ici .

3
t0mm13b
#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;
}
0
kgbook

Cette solution est plus une solution de contournement, mais elle a l’avantage d’être multi-plateforme. Il consiste à lire chaque caractère du premier fichier et à l'écrire dans le second. Voici le code (sans traitement d'erreur d'erreur d'ouverture de fichier):

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);
}
0
Donald Duck