Question

Cela ressemble à une question simple, mais je n’ai rien trouvé de semblable ici.

Puisqu'il n'y a pas de fonction de copie de fichier en C, nous devons implémenter nous-mêmes la copie de fichier, mais je n'aime pas réinventer la roue, même pour des choses aussi triviales, alors j'aimerais demander au cloud:

  1. Quel code recommanderiez-vous pour la copie de fichiers en utilisant fopen () / fread () / fwrite ()?
    • Quel code recommanderiez-vous pour la copie de fichiers avec open () / read () / write ()?

Ce code doit être portable (windows / mac / linux / bsd / qnx / younameit), stable, soumis à des tests de longue durée, rapide, économe en mémoire, etc. Il est recommandé d’entrer dans les éléments internes de systèmes spécifiques pour obtenir davantage de performances (comme avec le système de fichiers taille du cluster).

Cela semble être une question triviale mais, par exemple, le code source d'une commande CP ne correspond pas à 10 lignes de code C.

Était-ce utile?

La solution

En ce qui concerne les E / S réelles, le code que j'ai écrit un million de fois sous différentes formes pour copier des données d'un flux à un autre va dans ce sens. Il renvoie 0 en cas de succès, ou -1 avec errno ayant le code d'erreur erroné (auquel cas un nombre quelconque d'octets aurait pu être copié).

Notez que pour la copie de fichiers normaux, vous pouvez ignorer les commandes EAGAIN, car les fichiers normaux bloquent toujours les E / S. Mais inévitablement, si vous écrivez ce code, quelqu'un l'utilisera sur d'autres types de descripteurs de fichiers, alors considérez-le comme un cadeau.

Il y a une optimisation spécifique au fichier que GNU cp fait, ce que je n'ai pas dérangé ici, cela pour de longs blocs de 0 octets au lieu d'écrire, il vous suffit d'étendre le fichier de sortie en cherchant fin.

void block(int fd, int event) {
    pollfd topoll;
    topoll.fd = fd;
    topoll.events = event;
    poll(&topoll, 1, -1);
    // no need to check errors - if the stream is bust then the
    // next read/write will tell us
}

int copy_data_buffer(int fdin, int fdout, void *buf, size_t bufsize) {
    for(;;) {
       void *pos;
       // read data to buffer
       ssize_t bytestowrite = read(fdin, buf, bufsize);
       if (bytestowrite == 0) break; // end of input
       if (bytestowrite == -1) {
           if (errno == EINTR) continue; // signal handled
           if (errno == EAGAIN) {
               block(fdin, POLLIN);
               continue;
           }
           return -1; // error
       }

       // write data from buffer
       pos = buf;
       while (bytestowrite > 0) {
           ssize_t bytes_written = write(fdout, pos, bytestowrite);
           if (bytes_written == -1) {
               if (errno == EINTR) continue; // signal handled
               if (errno == EAGAIN) {
                   block(fdout, POLLOUT);
                   continue;
               }
               return -1; // error
           }
           bytestowrite -= bytes_written;
           pos += bytes_written;
       }
    }
    return 0; // success
}

// Default value. I think it will get close to maximum speed on most
// systems, short of using mmap etc. But porters / integrators
// might want to set it smaller, if the system is very memory
// constrained and they don't want this routine to starve
// concurrent ops of memory. And they might want to set it larger
// if I'm completely wrong and larger buffers improve performance.
// It's worth trying several MB at least once, although with huge
// allocations you have to watch for the linux 
// "crash on access instead of returning 0" behaviour for failed malloc.
#ifndef FILECOPY_BUFFER_SIZE
    #define FILECOPY_BUFFER_SIZE (64*1024)
#endif

int copy_data(int fdin, int fdout) {
    // optional exercise for reader: take the file size as a parameter,
    // and don't use a buffer any bigger than that. This prevents 
    // memory-hogging if FILECOPY_BUFFER_SIZE is very large and the file
    // is small.
    for (size_t bufsize = FILECOPY_BUFFER_SIZE; bufsize >= 256; bufsize /= 2) {
        void *buffer = malloc(bufsize);
        if (buffer != NULL) {
            int result = copy_data_buffer(fdin, fdout, buffer, bufsize);
            free(buffer);
            return result;
        }
    }
    // could use a stack buffer here instead of failing, if desired.
    // 128 bytes ought to fit on any stack worth having, but again
    // this could be made configurable.
    return -1; // errno is ENOMEM
}

Pour ouvrir le fichier d'entrée:

int fdin = open(infile, O_RDONLY|O_BINARY, 0);
if (fdin == -1) return -1;

Ouvrir le fichier de sortie est une astuce. En guise de base, vous voulez:

int fdout = open(outfile, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0x1ff);
if (fdout == -1) {
    close(fdin);
    return -1;
}

Mais il existe des facteurs de confusion:

  • vous devez utiliser des cas particuliers lorsque les fichiers sont identiques, et je ne me souviens plus comment faire de manière portable.
  • si le nom du fichier de sortie est un répertoire, vous pouvez copier le fichier dans le répertoire.
  • si le fichier de sortie existe déjà (ouvrez avec O_EXCL pour le déterminer et recherchez EEXIST en cas d'erreur), vous voudrez peut-être faire quelque chose de différent, comme le fait cp -i .
  • vous souhaiterez peut-être que les autorisations du fichier de sortie reflètent celles du fichier d'entrée.
  • vous souhaiterez peut-être copier d'autres métadonnées spécifiques à la plate-forme.
  • vous pouvez ou non dissocier le fichier de sortie en cas d'erreur.

Évidemment, les réponses à toutes ces questions pourraient être "faire la même chose que cp ". Dans ce cas, la réponse à la question initiale est "ignore tout ce que j'ai dit ou que quelqu'un d'autre a dit, et utilise la source de cp ".

Btw, obtenir la taille de la grappe du système de fichiers est presque inutile. Vous verrez presque toujours la vitesse augmenter avec la taille de la mémoire tampon longtemps après que vous ayez dépassé la taille d'un bloc de disque.

Autres conseils

C’est la fonction que j’utilise lorsque je dois copier d’un fichier à un autre - avec le faisceau de test:

/*
@(#)File:           $RCSfile: fcopy.c,v $
@(#)Version:        $Revision: 1.11 $
@(#)Last changed:   $Date: 2008/02/11 07:28:06 $
@(#)Purpose:        Copy the rest of file1 to file2
@(#)Author:         J Leffler
@(#)Modified:       1991,1997,2000,2003,2005,2008
*/

/*TABSTOP=4*/

#include "jlss.h"
#include "stderr.h"

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_fcopy_c[] = "@(#)$Id: fcopy.c,v 1.11 2008/02/11 07:28:06 jleffler Exp 
#include "stderr.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void err_syserr(const char *fmt, ...)
{
    int errnum = errno;
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(1);
}
quot;; #endif /* lint */ void fcopy(FILE *f1, FILE *f2) { char buffer[BUFSIZ]; size_t n; while ((n = fread(buffer, sizeof(char), sizeof(buffer), f1)) > 0) { if (fwrite(buffer, sizeof(char), n, f2) != n) err_syserr("write failed\n"); } } #ifdef TEST int main(int argc, char **argv) { FILE *fp1; FILE *fp2; err_setarg0(argv[0]); if (argc != 3) err_usage("from to"); if ((fp1 = fopen(argv[1], "rb")) == 0) err_syserr("cannot open file %s for reading\n", argv[1]); if ((fp2 = fopen(argv[2], "wb")) == 0) err_syserr("cannot open file %s for writing\n", argv[2]); fcopy(fp1, fp2); return(0); } #endif /* TEST */

Il est clair que cette version utilise des pointeurs de fichier provenant d’entrées / sorties standard et non des descripteurs de fichier, mais elle est relativement efficace et aussi portable que possible.

Eh bien, à l'exception de la fonction d'erreur, cela m'est propre. Tant que vous gérez les erreurs proprement, tout devrait bien se passer. L'en-tête "jlss.h" déclare fcopy () ; l'en-tête " stderr.h " déclare err_syserr () parmi de nombreuses autres fonctions de rapport d'erreur similaires. Une version simple de la fonction suit: la version réelle ajoute le nom du programme et effectue d'autres tâches.

<*>

Le code ci-dessus peut être considéré comme ayant une licence BSD moderne ou une licence GPL v3 au choix.

la taille de chaque lecture doit être un multiple de 512 (taille de secteur). 4096 est un bon

Voici un exemple très simple et clair: Copier un fichier . Comme il est écrit en ANSI-C sans appel de fonction particulière, je pense que celui-ci serait plutôt portable.

Selon ce que vous entendez par copier un fichier, il est certainement loin d’être trivial. Si vous voulez uniquement copier le contenu, il n’ya presque rien à faire. Mais généralement, vous devez copier les métadonnées du fichier, ce qui dépend sûrement de la plate-forme. Je ne connais aucune bibliothèque C qui fasse ce que vous voulez de manière portable. Traiter le nom de fichier seul n'est pas une mince affaire si vous vous souciez de la portabilité.

En C ++, il existe la bibliothèque de fichiers dans boost

Une chose que j'ai trouvée lors de l'implémentation de ma propre copie de fichier est évidente, mais ce n'est pas le cas: les E / S sont lentes . Vous pouvez à peu près calculer la vitesse de votre copie en fonction du nombre de copies que vous effectuez. Vous devez donc en faire le moins possible.

Les meilleurs résultats que j'ai trouvés sont lorsque je me suis procuré un tampon ginoureux, que j'ai lu le fichier source entier dans une seule E / S, puis que j'ai réécrit l'intégralité de la mémoire tampon dans une seule E / S. Si je devais même le faire en 10 lots, cela devenait très lent. Essayer de lire et d’écrire chaque octet, comme un premier codeur pourrait essayer en premier, était juste pénible.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top