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