efl: add eina_file_copy()
authorGustavo Sverzut Barbieri <barbieri@gmail.com>
Mon, 31 Dec 2012 23:17:18 +0000 (23:17 +0000)
committerGustavo Sverzut Barbieri <barbieri@gmail.com>
Mon, 31 Dec 2012 23:17:18 +0000 (23:17 +0000)
it's useful to copy file from one place to another and this will be
used in eio' s implementation.

NOTE: did not use mmap here as mmap faults may be cumbersome to handle
(Eina_File itself does that, but in a nasty way) and the
implementation would be severely different as there is no Eina_File
from FD, and there is no way to inject custom memory/fd into the
Eina_File's fault handling. The performance would not be that
different anyways and the splice() is already in there for systems
with good performance (read: Linux).

SVN revision: 81942

ChangeLog
NEWS
src/examples/eina/Makefile.am
src/examples/eina/eina_file_02.c [new file with mode: 0644]
src/lib/eina/eina_file.c
src/lib/eina/eina_file.h

index fc12aad..0120b3f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,7 @@
        * Added eina_xattr_fd_get(), eina_xattr_fd_set(),
        eina_xattr_del(), eina_xattr_fd_del(), eina_xattr_copy() and
        eina_xattr_fd_copy()
+       * Added eina_file_copy()
 
 2012-12-24  Mike Blumenkrantz
 
diff --git a/NEWS b/NEWS
index e058062..405ee99 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -46,6 +46,7 @@ Additions:
     * Added eina_xattr_fd_get(), eina_xattr_fd_set(),
       eina_xattr_del(), eina_xattr_fd_del(), eina_xattr_copy() and
       eina_xattr_fd_copy()
+    * Added eina_file_copy()
 
 Deprecations:
     * ecore_x:
index f35db42..4404b11 100644 (file)
@@ -13,6 +13,7 @@ eina_array_01.c \
 eina_array_02.c \
 eina_error_01.c \
 eina_file_01.c \
+eina_file_02.c \
 eina_hash_01.c \
 eina_hash_02.c \
 eina_hash_03.c \
@@ -56,6 +57,7 @@ eina_array_01 \
 eina_array_02 \
 eina_error_01 \
 eina_file_01 \
+eina_file_02 \
 eina_hash_01 \
 eina_hash_02 \
 eina_hash_03 \
diff --git a/src/examples/eina/eina_file_02.c b/src/examples/eina/eina_file_02.c
new file mode 100644 (file)
index 0000000..b77dcb4
--- /dev/null
@@ -0,0 +1,37 @@
+//Compile with:
+//gcc -g eina_file_02.c -o eina_file_02 `pkg-config --cflags --libs eina`
+
+#include <stdio.h>
+#include <Eina.h>
+
+static Eina_Bool
+_progress_cb(void *data, unsigned long long done, unsigned long long total)
+{
+   const char **files = data;
+   printf("%5llu/%llu of copy '%s' to '%s'\n", done, total, files[0], files[1]);
+   return EINA_TRUE;
+}
+
+int
+main(int argc, char **argv)
+{
+   Eina_Bool ret;
+
+   if (argc != 3)
+     {
+        fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
+        return EXIT_FAILURE;
+     }
+
+   eina_init();
+
+   ret = eina_file_copy(argv[1], argv[2],
+                        EINA_FILE_COPY_PERMISSION | EINA_FILE_COPY_XATTR,
+                        _progress_cb, argv + 1);
+
+   printf("copy finished: %s\n", ret ? "success" : "failure");
+
+   eina_shutdown();
+
+   return 0;
+}
index ec83b7d..01b193a 100644 (file)
@@ -54,6 +54,7 @@ void *alloca (size_t);
 #include <fcntl.h>
 
 #define PATH_DELIM '/'
+#define COPY_BLOCKSIZE (4 * 1024 * 1024)
 
 #include "eina_config.h"
 #include "eina_private.h"
@@ -95,6 +96,11 @@ void *alloca (size_t);
 #endif
 #define WRN(...) EINA_LOG_DOM_WARN(_eina_file_log_dom, __VA_ARGS__)
 
+#ifdef INF
+#undef INF
+#endif
+#define INF(...) EINA_LOG_DOM_INFO(_eina_file_log_dom, __VA_ARGS__)
+
 #ifdef DBG
 #undef DBG
 #endif
@@ -1518,3 +1524,256 @@ eina_file_statat(void *container, Eina_File_Direct_Info *info, Eina_Stat *st)
 #endif
    return 0;
 }
+
+static Eina_Bool
+_eina_file_copy_write_internal(int fd, char *buf, size_t size)
+{
+   size_t done = 0;
+   while (done < size)
+     {
+        ssize_t w = write(fd, buf + done, size - done);
+        if (w >= 0)
+          done += w;
+        else if ((errno != EAGAIN) && (errno != EINTR))
+          {
+             ERR("Error writing destination file during copy: %s",
+                 strerror(errno));
+             return EINA_FALSE;
+          }
+     }
+   return EINA_TRUE;
+}
+
+static Eina_Bool
+_eina_file_copy_read_internal(int fd, char *buf, off_t bufsize, ssize_t *readsize)
+{
+   while (1)
+     {
+        ssize_t r = read(fd, buf, bufsize);
+        if (r == 0)
+          {
+             ERR("Premature end of source file during copy.");
+             return EINA_FALSE;
+          }
+        else if (r < 0)
+          {
+             if ((errno != EAGAIN) && (errno != EINTR))
+               {
+                  ERR("Error reading source file during copy: %s",
+                      strerror(errno));
+                  return EINA_FALSE;
+               }
+          }
+        else
+          {
+             *readsize = r;
+             return EINA_TRUE;
+          }
+     }
+}
+
+static Eina_Bool
+_eina_file_copy_write_splice_internal(int fd, int pipefd, size_t size)
+{
+   size_t done = 0;
+   while (done < size)
+     {
+        ssize_t w = splice(pipefd, NULL, fd, NULL, size - done, SPLICE_F_MORE);
+        if (w >= 0)
+          done += w;
+        else if (errno == EINVAL)
+          {
+             INF("Splicing is not supported for destination file");
+             return EINA_FALSE;
+          }
+        else if ((errno != EAGAIN) && (errno != EINTR))
+          {
+             ERR("Error splicing to destination file during copy: %s",
+                 strerror(errno));
+             return EINA_FALSE;
+          }
+     }
+   return EINA_TRUE;
+}
+
+static Eina_Bool
+_eina_file_copy_read_splice_internal(int fd, int pipefd, off_t bufsize, ssize_t *readsize)
+{
+   while (1)
+     {
+        ssize_t r = splice(fd, NULL, pipefd, NULL, bufsize, SPLICE_F_MORE);
+        if (r == 0)
+          {
+             ERR("Premature end of source file during splice.");
+             return EINA_FALSE;
+          }
+        else if (r < 0)
+          {
+             if (errno == EINVAL)
+               {
+                  INF("Splicing is not supported for source file");
+                  return EINA_FALSE;
+               }
+             else if ((errno != EAGAIN) && (errno != EINTR))
+               {
+                  ERR("Error splicing from source file during copy: %s",
+                      strerror(errno));
+                  return EINA_FALSE;
+               }
+          }
+        else
+          {
+             *readsize = r;
+             return EINA_TRUE;
+          }
+     }
+}
+
+static Eina_Bool
+_eina_file_copy_splice_internal(int s, int d, off_t total, Eina_File_Copy_Progress cb, const void *cb_data, Eina_Bool *splice_unsupported)
+{
+#ifdef HAVE_SPLICE
+   off_t bufsize = COPY_BLOCKSIZE;
+   off_t done;
+   Eina_Bool ret;
+   int pipefd[2];
+
+   *splice_unsupported = EINA_TRUE;
+
+   if (pipe(pipefd) < 0) return EINA_FALSE;
+
+   done = 0;
+   ret = EINA_TRUE;
+   while (done < total)
+     {
+        size_t todo;
+        ssize_t r;
+
+        if (done + bufsize < total)
+          todo = bufsize;
+        else
+          todo = total - done;
+
+        printf("loop done=%lld, total=%lld, todo=%zd\n", done, total, todo);
+
+        ret = _eina_file_copy_read_splice_internal(s, pipefd[1], todo, &r);
+        if (!ret) break;
+
+        ret = _eina_file_copy_write_splice_internal(d, pipefd[0], r);
+        if (!ret) break;
+
+        *splice_unsupported = EINA_FALSE;
+        done += r;
+
+        if (cb)
+          {
+             ret = cb((void *)cb_data, done, total);
+             if (!ret) break;
+          }
+     }
+
+   close(pipefd[0]);
+   close(pipefd[1]);
+
+   return ret;
+#endif
+   *splice_unsupported = EINA_TRUE;
+   return EINA_FALSE;
+   (void)s;
+   (void)d;
+   (void)total;
+   (void)cb;
+   (void)cb_data;
+}
+
+static Eina_Bool
+_eina_file_copy_internal(int s, int d, off_t total, Eina_File_Copy_Progress cb, const void *cb_data)
+{
+   void *buf = NULL;
+   off_t bufsize = COPY_BLOCKSIZE;
+   off_t done;
+   Eina_Bool ret, splice_unsupported;
+
+   ret = _eina_file_copy_splice_internal(s, d, total, cb, cb_data,
+                                         &splice_unsupported);
+   if (ret)
+     return EINA_TRUE;
+   else if (!splice_unsupported) /* splice works, but copy failed anyway */
+     return EINA_FALSE;
+
+   /* make sure splice didn't change the position */
+   lseek(s, 0, SEEK_SET);
+   lseek(d, 0, SEEK_SET);
+
+   while ((bufsize > 0) && ((buf = malloc(bufsize)) == NULL))
+     bufsize /= 128;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(buf, EINA_FALSE);
+
+   done = 0;
+   ret = EINA_TRUE;
+   while (done < total)
+     {
+        size_t todo;
+        ssize_t r;
+
+        if (done + bufsize < total)
+          todo = bufsize;
+        else
+          todo = total - done;
+
+        ret = _eina_file_copy_read_internal(s, buf, todo, &r);
+        if (!ret) break;
+
+        ret = _eina_file_copy_write_internal(d, buf, r);
+        if (!ret) break;
+
+        done += r;
+
+        if (cb)
+          {
+             ret = cb((void *)cb_data, done, total);
+             if (!ret) break;
+          }
+     }
+
+   free(buf);
+   return ret;
+}
+
+EAPI Eina_Bool
+eina_file_copy(const char *src, const char *dst, Eina_File_Copy_Flags flags, Eina_File_Copy_Progress cb, const void *cb_data)
+{
+   struct stat st;
+   int s, d;
+   Eina_Bool success;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(src, EINA_FALSE);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(dst, EINA_FALSE);
+
+   s = open(src, O_RDONLY);
+   EINA_SAFETY_ON_TRUE_RETURN_VAL (s < 0, EINA_FALSE);
+
+   success = (fstat(s, &st) == 0);
+   EINA_SAFETY_ON_FALSE_GOTO(success, end);
+
+   d = open(dst, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+   EINA_SAFETY_ON_TRUE_GOTO(d < 0, end);
+
+   success = _eina_file_copy_internal(s, d, st.st_size, cb, cb_data);
+   if (success)
+     {
+        if (flags & EINA_FILE_COPY_PERMISSION)
+          fchmod(d, st.st_mode);
+        if (flags & EINA_FILE_COPY_XATTR)
+          eina_xattr_fd_copy(s, d);
+     }
+
+ end:
+   close(s);
+
+   if (!success)
+     unlink(dst);
+
+   return success;
+}
index 31c4eb5..718303c 100644 (file)
@@ -369,6 +369,46 @@ EAPI Eina_Iterator *eina_file_direct_ls(const char *dir) EINA_WARN_UNUSED_RESULT
  */
 EAPI char *eina_file_path_sanitize(const char *path);
 
+
+/**
+ * @typedef Eina_File_Copy_Progress
+ * used to report progress during eina_file_copy(), where @c done
+ * is the bytes already copied and @c size is the total file size.
+ *
+ * If returns #EINA_FALSE, it will stop the copy.
+ */
+typedef Eina_Bool (*Eina_File_Copy_Progress)(void *data, unsigned long long done, unsigned long long total);
+
+/**
+ * @typedef Eina_File_Copy_Flags
+ * what to copy from file.
+ */
+typedef enum {
+  EINA_FILE_COPY_DATA       = 0,
+  EINA_FILE_COPY_PERMISSION = (1 << 0),
+  EINA_FILE_COPY_XATTR      = (1 << 1)
+} Eina_File_Copy_Flags;
+
+/**
+ * Copy one file to another using the fastest possible way, report progress.
+ *
+ * This function will try  splice if it is available. It
+ * will block until the whole file is copied or it fails.
+ *
+ * During the progress it may call back @a cb with the progress summary.
+ *
+ * @param src the source file.
+ * @param dst the destination file.
+ * @param flags controls what is copied (data is always copied).
+ * @param cb if provided will be called with file copy progress information.
+ * @param cb_data context data to provide to @a cb during copy.
+ * @return #EINA_TRUE on success, #EINA_FALSE otherwise (and @a dst
+ *         will be deleted)
+ */
+EAPI Eina_Bool eina_file_copy(const char *src, const char *dst, Eina_File_Copy_Flags flags, Eina_File_Copy_Progress cb, const void *cb_data) EINA_ARG_NONNULL(1, 2);
+
+
+
 /**
  * @brief Get a read-only handler to a file.
  *