From 6ae6f925a4871d1be22221dbd5b1f2b09b1a172d Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 31 Dec 2012 23:17:18 +0000 Subject: [PATCH] efl: add eina_file_copy() 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 | 1 + NEWS | 1 + src/examples/eina/Makefile.am | 2 + src/examples/eina/eina_file_02.c | 37 +++++ src/lib/eina/eina_file.c | 259 +++++++++++++++++++++++++++++++ src/lib/eina/eina_file.h | 40 +++++ 6 files changed, 340 insertions(+) create mode 100644 src/examples/eina/eina_file_02.c diff --git a/ChangeLog b/ChangeLog index fc12aad747..0120b3ff23 100644 --- 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 e058062758..405ee995f9 100644 --- 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: diff --git a/src/examples/eina/Makefile.am b/src/examples/eina/Makefile.am index f35db42840..4404b119b4 100644 --- a/src/examples/eina/Makefile.am +++ b/src/examples/eina/Makefile.am @@ -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 index 0000000000..b77dcb4281 --- /dev/null +++ b/src/examples/eina/eina_file_02.c @@ -0,0 +1,37 @@ +//Compile with: +//gcc -g eina_file_02.c -o eina_file_02 `pkg-config --cflags --libs eina` + +#include +#include + +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 \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; +} diff --git a/src/lib/eina/eina_file.c b/src/lib/eina/eina_file.c index ec83b7d6e5..01b193af4f 100644 --- a/src/lib/eina/eina_file.c +++ b/src/lib/eina/eina_file.c @@ -54,6 +54,7 @@ void *alloca (size_t); #include #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; +} diff --git a/src/lib/eina/eina_file.h b/src/lib/eina/eina_file.h index 31c4eb5b62..718303c569 100644 --- a/src/lib/eina/eina_file.h +++ b/src/lib/eina/eina_file.h @@ -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. *