1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "nfs-workarounds.h"
7 #include "maildir-storage.h"
8 #include "maildir-uidlist.h"
9 #include "maildir-filename.h"
10 #include "maildir-keywords.h"
11 #include "maildir-sync.h"
12 #include "index-mail.h"
13 #include "mail-copy.h"
14 
15 #include <unistd.h>
16 #include <sys/stat.h>
17 
18 struct hardlink_ctx {
19 	const char *dest_path;
20 	bool success:1;
21 };
22 
do_hardlink(struct maildir_mailbox * mbox,const char * path,struct hardlink_ctx * ctx)23 static int do_hardlink(struct maildir_mailbox *mbox, const char *path,
24 		       struct hardlink_ctx *ctx)
25 {
26 	int ret;
27 
28 	if (mbox->storage->storage.set->mail_nfs_storage)
29 		ret = nfs_safe_link(path, ctx->dest_path, FALSE);
30 	else
31 		ret = link(path, ctx->dest_path);
32 	if (ret < 0) {
33 		if (errno == ENOENT)
34 			return 0;
35 
36 		if (ENOQUOTA(errno)) {
37 			mail_storage_set_error(&mbox->storage->storage,
38 				MAIL_ERROR_NOQUOTA, MAIL_ERRSTR_NO_QUOTA);
39 			return -1;
40 		}
41 
42 		/* we could handle the EEXIST condition by changing the
43 		   filename, but it practically never happens so just fallback
44 		   to standard copying for the rare cases when it does. */
45 		if (errno == EACCES || ECANTLINK(errno) || errno == EEXIST)
46 			return 1;
47 
48 		mailbox_set_critical(&mbox->box, "link(%s, %s) failed: %m",
49 				     path, ctx->dest_path);
50 		return -1;
51 	}
52 
53 	ctx->success = TRUE;
54 	return 1;
55 }
56 
57 static int
maildir_copy_hardlink(struct mail_save_context * ctx,struct mail * mail)58 maildir_copy_hardlink(struct mail_save_context *ctx, struct mail *mail)
59 {
60 	struct maildir_mailbox *dest_mbox = MAILDIR_MAILBOX(ctx->transaction->box);
61 	struct maildir_mailbox *src_mbox;
62 	struct maildir_filename *mf;
63 	struct hardlink_ctx do_ctx;
64 	const char *path, *guid, *dest_fname;
65 	uoff_t vsize, size;
66 	enum mail_lookup_abort old_abort;
67 
68 	if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0)
69 		src_mbox = MAILDIR_MAILBOX(mail->box);
70 	else if (strcmp(mail->box->storage->name, "raw") == 0) {
71 		/* lda uses raw format */
72 		src_mbox = NULL;
73 	} else {
74 		/* Can't hard link files from the source storage */
75 		return 0;
76 	}
77 
78 	/* hard link to tmp/ with a newly generated filename and later when we
79 	   have uidlist locked, move it to new/cur. */
80 	dest_fname = maildir_filename_generate();
81 	i_zero(&do_ctx);
82 	do_ctx.dest_path =
83 		t_strdup_printf("%s/tmp/%s", mailbox_get_path(&dest_mbox->box),
84 				dest_fname);
85 	if (src_mbox != NULL) {
86 		/* maildir */
87 		if (maildir_file_do(src_mbox, mail->uid,
88 				    do_hardlink, &do_ctx) < 0)
89 			return -1;
90 	} else {
91 		/* raw / lda */
92 		if (mail_get_special(mail, MAIL_FETCH_STORAGE_ID,
93 				     &path) < 0 || *path == '\0')
94 			return 0;
95 		if (do_hardlink(dest_mbox, path, &do_ctx) < 0)
96 			return -1;
97 	}
98 
99 	if (!do_ctx.success) {
100 		/* couldn't copy with hardlinking, fallback to copying */
101 		return 0;
102 	}
103 
104 	/* hardlinked to tmp/, treat as normal copied mail */
105 	mf = maildir_save_add(ctx, dest_fname, mail);
106 	if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) == 0) {
107 		if (*guid != '\0')
108 			maildir_save_set_dest_basename(ctx, mf, guid);
109 	}
110 
111 	/* finish copying keywords */
112 	maildir_save_finish_keywords(ctx);
113 
114 	/* remember size/vsize if possible */
115 	old_abort = mail->lookup_abort;
116 	mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
117 	if (mail_get_physical_size(mail, &size) < 0)
118 		size = UOFF_T_MAX;
119 	if (mail_get_virtual_size(mail, &vsize) < 0)
120 		vsize = UOFF_T_MAX;
121 	maildir_save_set_sizes(mf, size, vsize);
122 	mail->lookup_abort = old_abort;
123 	return 1;
124 }
125 
maildir_copy(struct mail_save_context * ctx,struct mail * mail)126 int maildir_copy(struct mail_save_context *ctx, struct mail *mail)
127 {
128 	struct mailbox_transaction_context *_t = ctx->transaction;
129 	struct maildir_mailbox *mbox = MAILDIR_MAILBOX(_t->box);
130 	int ret;
131 
132 	i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
133 
134 	if (mbox->storage->set->maildir_copy_with_hardlinks &&
135 	    mail_storage_copy_can_use_hardlink(mail->box, &mbox->box)) {
136 		T_BEGIN {
137 			ret = maildir_copy_hardlink(ctx, mail);
138 		} T_END;
139 
140 		if (ret != 0) {
141 			index_save_context_free(ctx);
142 			return ret > 0 ? 0 : -1;
143 		}
144 
145 		/* non-fatal hardlinking failure, try the slow way */
146 	}
147 
148 	return mail_storage_copy(ctx, mail);
149 }
150