diff options
Diffstat (limited to 'src/lib/file-copy.c')
-rw-r--r-- | src/lib/file-copy.c | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/src/lib/file-copy.c b/src/lib/file-copy.c new file mode 100644 index 0000000..c9b8e3e --- /dev/null +++ b/src/lib/file-copy.c @@ -0,0 +1,125 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream.h" +#include "file-copy.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +static int file_copy_to_tmp(const char *srcpath, const char *tmppath, + bool try_hardlink) +{ + struct istream *input; + struct ostream *output; + struct stat st; + mode_t old_umask; + int fd_in, fd_out; + int ret = -1; + + if (try_hardlink) { + /* see if hardlinking works */ + if (link(srcpath, tmppath) == 0) + return 1; + if (errno == EEXIST) { + if (i_unlink_if_exists(tmppath) < 0) + return -1; + if (link(srcpath, tmppath) == 0) + return 1; + } + if (errno == ENOENT) + return 0; + if (!ECANTLINK(errno)) { + i_error("link(%s, %s) failed: %m", srcpath, tmppath); + return -1; + } + + /* fallback to manual copying */ + } + + fd_in = open(srcpath, O_RDONLY); + if (fd_in == -1) { + if (errno == ENOENT) + return 0; + i_error("open(%s) failed: %m", srcpath); + return -1; + } + + if (fstat(fd_in, &st) < 0) { + i_error("fstat(%s) failed: %m", srcpath); + i_close_fd(&fd_in); + return -1; + } + + old_umask = umask(0); + fd_out = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode); + umask(old_umask); + if (fd_out == -1) { + i_error("open(%s, O_CREAT) failed: %m", tmppath); + i_close_fd(&fd_in); + return -1; + } + + /* try to change the group, don't really care if it fails */ + if (fchown(fd_out, (uid_t)-1, st.st_gid) < 0 && errno != EPERM) + i_error("fchown(%s) failed: %m", tmppath); + + input = i_stream_create_fd(fd_in, IO_BLOCK_SIZE); + output = o_stream_create_fd_file(fd_out, 0, FALSE); + + switch (o_stream_send_istream(output, input)) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + ret = 0; + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + i_unreached(); + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + i_error("read(%s) failed: %s", srcpath, + i_stream_get_error(input)); + break; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + i_error("write(%s) failed: %s", tmppath, + o_stream_get_error(output)); + break; + } + + i_stream_destroy(&input); + o_stream_destroy(&output); + + if (close(fd_in) < 0) { + i_error("close(%s) failed: %m", srcpath); + ret = -1; + } + if (close(fd_out) < 0) { + i_error("close(%s) failed: %m", tmppath); + ret = -1; + } + return ret < 0 ? -1 : 1; +} + +int file_copy(const char *srcpath, const char *destpath, bool try_hardlink) +{ + int ret; + + T_BEGIN { + const char *tmppath; + + tmppath = t_strconcat(destpath, ".tmp", NULL); + + ret = file_copy_to_tmp(srcpath, tmppath, try_hardlink); + if (ret > 0) { + if (rename(tmppath, destpath) < 0) { + i_error("rename(%s, %s) failed: %m", + tmppath, destpath); + ret = -1; + } + } + if (ret < 0) + i_unlink(tmppath); + } T_END; + return ret; +} |