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

14 minutos de lectura

avatar de usuario
Motti

Estoy buscando el equivalente Unix de Win32 Copiar archivono quiero reinventar la rueda escribiendo mi propia versión.

  • Para no reinventar la rueda compila GNU coreutils, AFAIK tiene una biblioteca estática para copiar archivos en su árbol de compilación, utilizada por cp y otros. Admite escasez y vaca btrfs

    – cuenca

    11/09/2016 a las 13:39

  • subconjunto de Linux: stackoverflow.com/questions/7463689/…

    – Ciro Santilli Путлер Капут 六四事

    5 de abril de 2019 a las 10:58

avatar de usuario
coste y flete

No hay necesidad de llamar a las API no portátiles como sendfile, o pagar a utilidades externas. El mismo método que funcionó en los años 70 todavía funciona ahora:

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

  • @Caf: OMG….goto…. 🙂 Tu código es más sensato que el mío de todos modos… 😉 El viejo bucle con lectura/escritura es el más portátil… +1 de mi parte…

    – t0mm13b

    2 de febrero de 2010 a las 0:03

  • Encuentro un uso controlado de goto puede ser útil para consolidar la ruta de manejo de errores en un solo lugar.

    – café

    2 de febrero de 2010 a las 0:36

  • No utilizable para uso general. Una copia de un archivo es más que solo el flujo de datos. ¿Qué tal archivos dispersos o atributos extendidos? Esa es una vez más la razón por la que la API de Windows, tan fea como es, supera a Linux

    – Lotario

    17 de julio de 2012 a las 21:22

  • te encargas EINTR en el write() bucle, pero no en el read() lazo.

    – Jonathan Reinhart

    22 de abril de 2015 a las 17:33

  • Los archivos @Lothar Unix son conceptualmente solo una secuencia de bytes. Los metadatos, como permisos, ACL, etc., se manejan de forma ortogonal a la copia real de los datos. Como deberían ser. Los formatos de archivo específicos de la aplicación son el problema de la aplicación. Como deberían ser.

    – wcochran

    20 de febrero de 2018 a las 19:11

avatar de usuario
Mahmoud Al-Qudsi

No hay una función CopyFile equivalente integrada en las API. Pero enviar archivo se puede usar para copiar un archivo en modo kernel, que es una solución mejor y más rápida (por numerosas razones) que abrir un archivo, recorrerlo para leerlo en un búfer y escribir la salida en otro archivo.

Actualizar:

A partir de la versión 2.6.33 del kernel de Linux, la limitación que requiere la salida de sendfile para ser un socket se eliminó 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 que la salida sea un socket 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 de “copia cero” para todas las plataformas, lo que significa que todo se realiza en el espacio del kernel y no hay copia de búferes o datos dentro y fuera del espacio del usuario. Prácticamente el mejor rendimiento que puede obtener.

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

EDITAR: Se reemplazó la apertura del destino con la llamada a creat() como queremos la bandera O_TRUNC para ser especificado Ver comentario a continuación.

  • De acuerdo con la página del manual, el argumento de salida de sendfile debe ser un enchufe. ¿Estás seguro de que esto funciona?

    – Jay Conrod

    1 de febrero de 2010 a las 21:27

  • Para Linux, Jay Conrod tiene razón: el out_fd de sendfile podría ser un archivo normal en kernels 2.4, pero ahora debe admitir el sendpage API interna del núcleo (que esencialmente significa tubería o zócalo). sendpage se implementa de manera diferente en diferentes UNIX: no hay una semántica estándar para ello.

    – café

    1 de febrero de 2010 a las 21:45


  • El prototipo en Linux es diferente a OSX, por lo tanto, pensaría (y yo también lo pensé) que cuando vi su implementación y vi los parámetros adicionales para el archivo de envío… depende de la plataforma, ¡algo que vale la pena tener en cuenta!

    – t0mm13b

    1 de febrero de 2010 a las 21:59


  • para tu información, puedes ahorrar mucho trabajo con un if (PathsMatch (origen, destino)) return 1; /* donde PathsMatch es la rutina de comparación de ruta adecuada para la configuración regional */, de lo contrario, imagino que la segunda apertura fallaría.

    – pedestal

    2 de febrero de 2010 a las 1:44

  • +1 hombre enviar archivo dice que desde 2.6.33, esto es compatible nuevamente. sendfile() es superior a CopyFile() ya que permite un desplazamiento. Esto es útil para eliminar la información del encabezado de un archivo.

    – ruido sin arte

    25 de noviembre de 2013 a las 22:41

avatar de usuario
pedestal

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 es necesario desinfectar los argumentos en la misma medida. Además, dado que system() requiere que improvisar el argumento del comando, no es probable que tenga un problema de desbordamiento de búfer debido a una verificación descuidada de sprintf().

La ventaja de llamar a cp directamente en lugar de escribirlo es que no tiene que preocuparse por los elementos de la ruta de destino que existen en el destino. Hacer eso en código propio es propenso a errores y tedioso.

Escribí este ejemplo en ANSI C y solo eliminé el manejo de errores mínimo, aparte de eso, 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 */
        {
        }
    }
}

  • +1 por otro trabajo largo y detallado. Realmente te hace apreciar la forma “vector”/lista de system() en perl. Mmm. ¿Tal vez sería bueno tener una función similar al sistema con una matriz argv?

    – Roboprog

    1 de febrero de 2010 a las 23:20

  • … después de todo, se implementó hace 17 años en glibc, y siendo una función estándar, 10 años antes de que se escribiera su respuesta …

    – Antti Haapala — Слава Україні

    19/10/2017 a las 21:27

avatar de usuario
patricio schlüter

Otra variante de la función de copia usando llamadas POSIX normales y sin bucle alguno. Código inspirado en la variante de copia de búfer de la respuesta de caf. Advertencia: Uso mmap puede fallar fácilmente en sistemas de 32 bits, en sistemas de 64 bits el peligro es menos 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;
}

EDITAR: Corregido el error de creación de archivos. Consulte el comentario en la respuesta http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c/2180157#2180157.

avatar de usuario
t0mm13b

Hay una manera de hacer esto, sin recurrir a la system llamada, necesita incorporar un contenedor algo como esto:

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

El ejemplo anterior (¡se omite la comprobación de errores!) emplea open, close y sendfile.

Editar: Como coste y flete ha señalado un condición de carrera puede ocurrir entre el open y stat así que pensé en hacer esto un poco más robusto… Tenga en cuenta que el mecanismo de bloqueo varía de una plataforma a otra… bajo Linux, este mecanismo de bloqueo con lockf bastaría. Si desea que esto sea portátil, use el #ifdef macros para distinguir entre diferentes plataformas/compiladores… Gracias caf por detectar esto… Hay un enlace a un sitio que produjo “rutinas de bloqueo universal” aquí.

  • No estoy 100% seguro sobre el prototipo del archivo de envío, creo que me equivoqué en uno de los parámetros… por favor, tenlo en cuenta… 🙂

    – t0mm13b

    1 de febrero de 2010 a las 21:29

  • Tiene una condición de carrera: el archivo que ha abierto como fdSource y el archivo que tienes stat()ed no son necesariamente los mismos.

    – café

    1 de febrero de 2010 a las 22:30


  • @caf: ¿Puede dar más detalles mientras lo estoy viendo y cómo puede haber una condición de carrera? Modificaré la respuesta en consecuencia … gracias por avisarme …

    – t0mm13b

    1 de febrero de 2010 a las 23:53

  • tommbieb75: Simple – en medio de la open() llamar y el stat() llamada, otra persona podría haber cambiado el nombre del archivo y poner un archivo diferente con ese nombre, por lo que copiará los datos del primer archivo, pero usando la longitud del segundo.

    – café

    2 de febrero de 2010 a las 0:35

  • @caf: Holy moly… ¿por qué no pensé en eso… bien localizado… un candado debería hacer el truco en el archivo fuente… bien hecho por detectar eso… condición de carrera… bueno yo nunca… como dice Clint Eastwood en ‘Gran Torino’ ‘JC all friday…’

    – t0mm13b

    2 de febrero de 2010 a las 0:57

Copiar archivos byte a byte funciona, pero es lento y derrochador en los UNIX modernos. Los UNIX modernos tienen soporte de “copia en escritura” integrado en el sistema de archivos: una llamada al sistema crea una nueva entrada de directorio que apunta a los bytes existentes en el disco, y no se toca ningún byte de contenido de archivo en el disco hasta que se modifica una de las copias. , momento en el que solo los bloques modificados se escriben en el disco. Esto permite copias de archivos casi instantáneas que no utilizan bloques de archivos adicionales, independientemente del tamaño del archivo. Por ejemplo, aquí hay algunos detalles sobre como funciona esto en xfs.

En linux, usa los FICLONE ioctl como coreutils cp ahora lo hace por defecto.

 #ifdef FICLONE
   return ioctl (dest_fd, FICLONE, src_fd);
 #else
   errno = ENOTSUP;
   return -1;
 #endif

En macOS, use clonar archivo(2) para copias instantáneas en volúmenes APFS. Esto es lo de Apple cp -c usos. Los documentos no son completamente claros, pero es probable que copiar archivo (3) con COPYFILE_CLONE también usa esto. Deja un comentario si quieres que pruebe eso.

En caso de que estas operaciones de copia en escritura no sean compatibles, ya sea que el sistema operativo sea demasiado antiguo, el sistema de archivos subyacente no lo admita o porque esté copiando archivos entre diferentes sistemas de archivos, debe volver a probar sendfile, o como último recurso, copiando byte a byte. Pero para ahorrarles a todos mucho tiempo y espacio en el disco, déles FICLONE y clonefile(2) un intento primero.

  • No estoy 100% seguro sobre el prototipo del archivo de envío, creo que me equivoqué en uno de los parámetros… por favor, tenlo en cuenta… 🙂

    – t0mm13b

    1 de febrero de 2010 a las 21:29

  • Tiene una condición de carrera: el archivo que ha abierto como fdSource y el archivo que tienes stat()ed no son necesariamente los mismos.

    – café

    1 de febrero de 2010 a las 22:30


  • @caf: ¿Puede dar más detalles mientras lo estoy viendo y cómo puede haber una condición de carrera? Modificaré la respuesta en consecuencia … gracias por avisarme …

    – t0mm13b

    1 de febrero de 2010 a las 23:53

  • tommbieb75: Simple – en medio de la open() llamar y el stat() llamada, otra persona podría haber cambiado el nombre del archivo y poner un archivo diferente con ese nombre, por lo que copiará los datos del primer archivo, pero usando la longitud del segundo.

    – café

    2 de febrero de 2010 a las 0:35

  • @caf: Holy moly… ¿por qué no pensé en eso… bien localizado… un candado debería hacer el truco en el archivo fuente… bien hecho por detectar eso… condición de carrera… bueno yo nunca… como dice Clint Eastwood en ‘Gran Torino’ ‘JC all friday…’

    – t0mm13b

    2 de febrero de 2010 a las 0:57

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad