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