¿Cómo puedo copiar un archivo en Unix usando C?

Estoy buscando el equivalente de Unix de CopyFile de Win32, no quiero reinventar la rueda escribiendo mi propia versión.

No es necesario invocar API no portátiles, como sendfile , ni pagar por servicios externos. El mismo método que funcionó en los años 70 todavía funciona ahora:

 #include  #include  #include  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; } 

Es sencillo usar fork / execl para ejecutar cp y hacer el trabajo por usted. Esto tiene ventajas sobre el sistema en el sentido de que no es propenso a un ataque de Bobby Tables y no necesita desinfectar los argumentos en el mismo grado. Además, dado que system () requiere que improvise el argumento del comando, no es probable que tenga un problema de desbordamiento de búfer debido a la comprobación descuidada de sprintf ().

La ventaja de llamar a cp directamente en lugar de escribirlo no es tener que preocuparse por los elementos de la ruta de destino existente en el destino. Hacer eso en el código roll-you-own es propenso a errores y tedioso.

Escribí este ejemplo en ANSI C y solo supere el manejo más simple de errores, aparte de que es un código directo.

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

No hay una función CopyFile equivalente cocida en las API. Pero sendfile puede usarse para copiar un archivo en modo kernel, que es una solución más rápida y mejor (por numerosas razones) que abrir un archivo, alternarlo para leer en un búfer y escribir el resultado en otro archivo.

Actualizar:

A partir de Linux kernel versión 2.6.33, la limitación que requería que la salida de sendfile fuera un socket se levantó y el código original funcionaría tanto en Linux como, sin embargo, a partir de OS X 10.9 Mavericks, sendfile en OS X ahora requiere la salida para ser un zócalo y el código no funcionará!

El siguiente fragmento de código debería funcionar en la mayoría de OS X (a partir de 10.5), BSD (gratuito) y Linux (a partir de 2.6.33). La implementación es “copia cero” para todas las plataformas, lo que significa que todo se hace en el espacio del kernel y no hay copia de almacenamientos intermedios o datos dentro y fuera del espacio de usuario. Casi el mejor rendimiento que puedes obtener.

 #include  #include  #if defined(__APPLE__) || defined(__FreeBSD__) #include  #else #include  #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 (ie 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; } 

EDITAR : Reemplazó la apertura del destino con la llamada a creat() ya que queremos que se especifique el indicador O_TRUNC . Ver el comentario a continuación.

 sprintf( cmd, "/bin/cp -p \'%s\' \'%s\'", old, new); system( cmd); 

Agregue algunas verificaciones de error …

De lo contrario, abra ambos y bucle en lectura / escritura, pero probablemente no sea lo que desea.

ACTUALIZAR para abordar inquietudes de seguridad válidas:

En lugar de usar “system ()”, haga un fork / wait, y llame a execv () o execl () en el niño.

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

Hay una manera de hacer esto, sin recurrir a la llamada al system , necesita incorporar un wrapper como este:

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

La muestra anterior (se omite la comprobación de errores) emplea open , close y sendfile .

Editar: Como caf ha señalado que una condición de carrera puede ocurrir entre la open y la stat así que pensé en hacer esto un poco más robusto … Tenga en cuenta que el mecanismo de locking varía de una plataforma a otra … bajo Linux, este mecanismo de locking con lockf sería suficiente. Si desea hacer esto portátil, utilice las macros #ifdef para distinguir entre diferentes plataformas / comstackdores … Gracias caf por detectar esto … Aquí hay un enlace a un sitio que produjo “rutinas de locking universales”.

Otra variante de la función de copia utilizando llamadas POSIX normales y sin ningún bucle. Código inspirado en la variante de copia de búfer de la respuesta de caf. Advertencia: el uso de mmap puede fallar fácilmente en los sistemas de 32 bits, en el sistema de 64 bits el peligro es menos probable.

 #include  #include  #include  #include  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 : corrigió el error de creación del archivo. Vea el comentario en http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/2180157#2180157 answer.

Una opción es que podrías usar system() para ejecutar cp . Esto simplemente reutiliza el comando cp(1) para hacer el trabajo. Si solo necesita hacer otro enlace el archivo, esto puede hacerse con link() o symlink() .

Esta solución es más una solución, pero tiene la ventaja de ser multiplataforma. Consiste en leer cada carácter del primer archivo y escribirlo en el segundo archivo. Aquí está el código (sin manejo de errores de apertura de archivo):

 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); }