summaryrefslogtreecommitdiffstats
path: root/src/lib/file-copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/file-copy.c')
-rw-r--r--src/lib/file-copy.c125
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;
+}