comparison src/plugins/lazy-expunge/lazy-expunge-plugin.c @ 4913:dea1c8fa53f4 HEAD

Added lazy expunge plugin.
author Timo Sirainen <timo.sirainen@movial.fi>
date Sat, 16 Dec 2006 02:09:19 +0200
parents
children 651605333987
comparison
equal deleted inserted replaced
4912:b08e63f6dcfd 4913:dea1c8fa53f4
1 /* Copyright (C) 2006 Timo Sirainen */
2
3 #include "common.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "str.h"
7 #include "seq-range-array.h"
8 #include "maildir-storage.h"
9 #include "client.h"
10 #include "namespace.h"
11 #include "lazy-expunge-plugin.h"
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <dirent.h>
17 #include <time.h>
18
19 #define LAZY_EXPUNGE_CONTEXT(obj) \
20 *((void **)array_idx_modifiable(&(obj)->module_contexts, \
21 lazy_expunge_storage_module_id))
22
23 enum lazy_namespace {
24 LAZY_NAMESPACE_EXPUNGE,
25 LAZY_NAMESPACE_DELETE,
26 LAZY_NAMESPACE_DELETE_EXPUNGE,
27
28 LAZY_NAMESPACE_COUNT
29 };
30
31 struct lazy_expunge_mail_storage {
32 struct mail_storage_vfuncs super;
33 };
34
35 struct lazy_expunge_mailbox {
36 struct mailbox_vfuncs super;
37 };
38
39 struct lazy_expunge_transaction {
40 ARRAY_TYPE(seq_range) expunge_seqs;
41 struct mailbox *expunge_box;
42 };
43
44 struct lazy_expunge_mail {
45 struct mail_vfuncs super;
46 };
47
48 static void (*lazy_expunge_next_hook_mail_storage_created)
49 (struct mail_storage *storage);
50 static void (*lazy_expunge_next_hook_client_created)(struct client **client);
51
52 static unsigned int lazy_expunge_storage_module_id = 0;
53 static bool lazy_expunge_storage_module_id_set = FALSE;
54
55 static struct namespace *lazy_namespaces[LAZY_NAMESPACE_COUNT];
56
57 static struct mailbox *
58 mailbox_open_or_create(struct mail_storage *storage, const char *name)
59 {
60 struct mailbox *box;
61 bool syntax, temp;
62
63 box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST |
64 MAILBOX_OPEN_KEEP_RECENT);
65 if (box != NULL)
66 return box;
67
68 (void)mail_storage_get_last_error(storage, &syntax, &temp);
69 if (syntax || temp)
70 return NULL;
71
72 /* probably the mailbox just doesn't exist. try creating it. */
73 if (mail_storage_mailbox_create(storage, name, FALSE) < 0)
74 return NULL;
75
76 /* and try opening again */
77 box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST |
78 MAILBOX_OPEN_KEEP_RECENT);
79 return box;
80 }
81
82 static int lazy_expunge_mail_expunge(struct mail *_mail)
83 {
84 struct lazy_expunge_transaction *lt =
85 LAZY_EXPUNGE_CONTEXT(_mail->transaction);
86 struct mail_storage *deststorage;
87
88 if (lt->expunge_box == NULL) {
89 deststorage = lazy_namespaces[LAZY_NAMESPACE_EXPUNGE]->storage;
90 lt->expunge_box = mailbox_open_or_create(deststorage,
91 _mail->box->name);
92 if (lt->expunge_box == NULL) {
93 mail_storage_set_critical(_mail->box->storage,
94 "lazy_expunge: Couldn't open expunge mailbox");
95 return -1;
96 }
97 }
98
99 seq_range_array_add(&lt->expunge_seqs, 32, _mail->seq);
100 return 0;
101 }
102
103 static struct mailbox_transaction_context *
104 lazy_expunge_transaction_begin(struct mailbox *box,
105 enum mailbox_transaction_flags flags)
106 {
107 struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(box);
108 struct mailbox_transaction_context *t;
109 struct lazy_expunge_transaction *lt;
110
111 t = qbox->super.transaction_begin(box, flags);
112 lt = i_new(struct lazy_expunge_transaction, 1);
113
114 array_idx_set(&t->module_contexts, lazy_expunge_storage_module_id, &lt);
115 return t;
116 }
117
118 struct lazy_expunge_move_context {
119 string_t *path;
120 unsigned int dir_len;
121 };
122
123 static int lazy_expunge_move(struct maildir_mailbox *mbox __attr_unused__,
124 const char *path, void *context)
125 {
126 struct lazy_expunge_move_context *ctx = context;
127 const char *p;
128
129 str_truncate(ctx->path, ctx->dir_len);
130 p = strrchr(path, '/');
131 str_append(ctx->path, p == NULL ? path : p + 1);
132
133 if (rename(path, str_c(ctx->path)) == 0)
134 return 1;
135 return errno == ENOENT ? 0 : -1;
136 }
137
138 static int lazy_expunge_move_expunges(struct mailbox *srcbox,
139 struct lazy_expunge_transaction *lt)
140 {
141 struct maildir_mailbox *msrcbox = (struct maildir_mailbox *)srcbox;
142 struct mailbox_transaction_context *trans;
143 struct index_transaction_context *itrans;
144 struct lazy_expunge_move_context ctx;
145 const struct seq_range *range;
146 unsigned int i, count;
147 const char *dir;
148 uint32_t seq;
149 bool is_file;
150 int ret = 0;
151
152 dir = mail_storage_get_mailbox_path(lt->expunge_box->storage,
153 lt->expunge_box->name, &is_file);
154 dir = t_strconcat(dir, "/cur/", NULL);
155
156 ctx.path = str_new(default_pool, 256);
157 str_append(ctx.path, dir);
158 ctx.dir_len = str_len(ctx.path);
159
160 trans = mailbox_transaction_begin(srcbox,
161 MAILBOX_TRANSACTION_FLAG_EXTERNAL);
162 itrans = (struct index_transaction_context *)trans;
163
164 range = array_get(&lt->expunge_seqs, &count);
165 for (i = 0; i < count && ret == 0; i++) {
166 for (seq = range[i].seq1; seq <= range[i].seq2; seq++) {
167 ret = maildir_file_do(msrcbox, seq, lazy_expunge_move,
168 &ctx);
169 if (ret < 0)
170 break;
171
172 mail_index_expunge(itrans->trans, seq);
173 }
174 }
175
176 if (mailbox_transaction_commit(&trans, 0) < 0)
177 ret = -1;
178
179 str_free(&ctx.path);
180 return ret;
181 }
182
183 static void lazy_expunge_transaction_free(struct lazy_expunge_transaction *lt)
184 {
185 if (lt->expunge_box != NULL)
186 mailbox_close(&lt->expunge_box);
187 if (array_is_created(&lt->expunge_seqs))
188 array_free(&lt->expunge_seqs);
189 i_free(lt);
190 }
191
192 static int
193 lazy_expunge_transaction_commit(struct mailbox_transaction_context *ctx,
194 enum mailbox_sync_flags flags)
195 {
196 struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(ctx->box);
197 struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT(ctx);
198 struct mailbox *srcbox = ctx->box;
199 int ret;
200
201 ret = qbox->super.transaction_commit(ctx, flags);
202
203 if (ret == 0 && array_is_created(&lt->expunge_seqs))
204 ret = lazy_expunge_move_expunges(srcbox, lt);
205
206 lazy_expunge_transaction_free(lt);
207 return ret;
208 }
209
210 static void
211 lazy_expunge_transaction_rollback(struct mailbox_transaction_context *ctx)
212 {
213 struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(ctx->box);
214 struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT(ctx);
215
216 qbox->super.transaction_rollback(ctx);
217 lazy_expunge_transaction_free(lt);
218 }
219
220 static struct mail *
221 lazy_expunge_mail_alloc(struct mailbox_transaction_context *t,
222 enum mail_fetch_field wanted_fields,
223 struct mailbox_header_lookup_ctx *wanted_headers)
224 {
225 struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(t->box);
226 struct lazy_expunge_mail *lmail;
227 struct mail *_mail;
228 struct mail_private *mail;
229
230 _mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers);
231 mail = (struct mail_private *)_mail;
232
233 lmail = p_new(mail->pool, struct lazy_expunge_mail, 1);
234 lmail->super = mail->v;
235
236 mail->v.expunge = lazy_expunge_mail_expunge;
237 array_idx_set(&mail->module_contexts,
238 lazy_expunge_storage_module_id, &lmail);
239 return _mail;
240 }
241
242 static struct mailbox *
243 lazy_expunge_mailbox_open(struct mail_storage *storage, const char *name,
244 struct istream *input, enum mailbox_open_flags flags)
245 {
246 struct lazy_expunge_mail_storage *lstorage =
247 LAZY_EXPUNGE_CONTEXT(storage);
248 struct mailbox *box;
249 struct lazy_expunge_mailbox *qbox;
250
251 box = lstorage->super.mailbox_open(storage, name, input, flags);
252 if (box == NULL)
253 return NULL;
254
255 qbox = p_new(box->pool, struct lazy_expunge_mailbox, 1);
256 qbox->super = box->v;
257
258 box->v.transaction_begin = lazy_expunge_transaction_begin;
259 box->v.transaction_commit = lazy_expunge_transaction_commit;
260 box->v.transaction_rollback = lazy_expunge_transaction_rollback;
261 box->v.mail_alloc = lazy_expunge_mail_alloc;
262 array_idx_set(&box->module_contexts,
263 lazy_expunge_storage_module_id, &qbox);
264 return box;
265 }
266
267 static int dir_move_or_merge(struct mail_storage *storage,
268 const char *srcdir, const char *destdir)
269 {
270 DIR *dir;
271 struct dirent *dp;
272 string_t *src_path, *dest_path;
273 unsigned int src_dirlen, dest_dirlen;
274 int ret = 0;
275
276 if (rename(srcdir, destdir) == 0 || errno == ENOENT)
277 return 0;
278
279 if (!EDESTDIREXISTS(errno)) {
280 mail_storage_set_critical(storage,
281 "rename(%s, %s) failed: %m", srcdir, destdir);
282 }
283
284 /* rename all the files separately */
285 dir = opendir(srcdir);
286 if (dir == NULL) {
287 mail_storage_set_critical(storage,
288 "opendir(%s) failed: %m", srcdir);
289 return -1;
290 }
291
292 src_path = t_str_new(512);
293 dest_path = t_str_new(512);
294
295 str_append(src_path, srcdir);
296 str_append(dest_path, destdir);
297 src_dirlen = str_len(src_path);
298 dest_dirlen = str_len(dest_path);
299
300 while ((dp = readdir(dir)) != NULL) {
301 str_truncate(src_path, src_dirlen);
302 str_append(src_path, dp->d_name);
303 str_truncate(dest_path, dest_dirlen);
304 str_append(dest_path, dp->d_name);
305
306 if (rename(str_c(src_path), str_c(dest_path)) < 0 &&
307 errno != ENOENT) {
308 mail_storage_set_critical(storage,
309 "rename(%s, %s) failed: %m",
310 str_c(src_path), str_c(dest_path));
311 ret = -1;
312 }
313 }
314 if (closedir(dir) < 0) {
315 mail_storage_set_critical(storage,
316 "closedir(%s) failed: %m", srcdir);
317 ret = -1;
318 }
319 if (ret == 0) {
320 if (rmdir(srcdir) < 0) {
321 mail_storage_set_critical(storage,
322 "rmdir(%s) failed: %m", srcdir);
323 ret = -1;
324 }
325 }
326 return ret;
327 }
328
329 static int
330 mailbox_move(struct mail_storage *src_storage, const char *src_name,
331 struct mail_storage *dest_storage, const char **_dest_name)
332 {
333 const char *dest_name = *_dest_name;
334 const char *srcdir, *src2dir, *src3dir, *destdir;
335 bool is_file;
336
337 srcdir = mail_storage_get_mailbox_path(src_storage, src_name, &is_file);
338 destdir = mail_storage_get_mailbox_path(dest_storage, dest_name,
339 &is_file);
340 while (rename(srcdir, destdir) < 0) {
341 if (errno == ENOENT)
342 return 0;
343
344 if (!EDESTDIREXISTS(errno)) {
345 mail_storage_set_critical(src_storage,
346 "rename(%s, %s) failed: %m", srcdir, destdir);
347 return -1;
348 }
349
350 /* mailbox is being deleted multiple times per second.
351 update the filename. */
352 dest_name = t_strdup_printf("%s-%04u", *_dest_name,
353 (uint32_t)random());
354 destdir = mail_storage_get_mailbox_path(dest_storage, dest_name,
355 &is_file);
356 }
357
358 t_push();
359 src2dir = mail_storage_get_mailbox_control_dir(src_storage, src_name);
360 if (strcmp(src2dir, srcdir) != 0) {
361 destdir = mail_storage_get_mailbox_control_dir(dest_storage,
362 dest_name);
363 (void)dir_move_or_merge(src_storage, srcdir, destdir);
364 }
365 src3dir = mail_storage_get_mailbox_index_dir(src_storage, src_name);
366 if (strcmp(src3dir, srcdir) != 0 && strcmp(src3dir, src2dir) != 0) {
367 destdir = mail_storage_get_mailbox_index_dir(dest_storage,
368 dest_name);
369 (void)dir_move_or_merge(src_storage, srcdir, destdir);
370 }
371 t_pop();
372
373 *_dest_name = dest_name;
374 return 1;
375 }
376
377 static int
378 lazy_expunge_mailbox_delete(struct mail_storage *storage, const char *name)
379 {
380 struct mail_storage *dest_storage;
381 enum mailbox_name_status status;
382 const char *destname;
383 struct tm *tm;
384 char timestamp[256];
385 int ret;
386
387 mail_storage_clear_error(storage);
388
389 /* first do the normal sanity checks */
390 if (strcmp(name, "INBOX") == 0) {
391 mail_storage_set_error(storage, "INBOX can't be deleted.");
392 return -1;
393 }
394
395 if (mailbox_list_get_mailbox_name_status(storage->list, name,
396 &status) < 0)
397 return -1;
398 if (status == MAILBOX_NAME_INVALID) {
399 mail_storage_set_error(storage, "Invalid mailbox name");
400 return -1;
401 }
402
403 /* destination mailbox name needs to contain a timestamp */
404 tm = localtime(&ioloop_time);
405 if (strftime(timestamp, sizeof(timestamp), "%Y%m%d-%H%M%S", tm) == 0)
406 strocpy(timestamp, dec2str(ioloop_time), sizeof(timestamp));
407 destname = t_strconcat(name, "-", timestamp, NULL);
408
409 /* first move the actual mailbox */
410 dest_storage = lazy_namespaces[LAZY_NAMESPACE_DELETE]->storage;
411 if ((ret = mailbox_move(storage, name, dest_storage, &destname)) < 0)
412 return -1;
413 if (ret == 0) {
414 mail_storage_set_error(storage,
415 MAIL_STORAGE_ERR_MAILBOX_NOT_FOUND, name);
416 return -1;
417 }
418
419 /* next move the expunged messages mailbox, if it exists */
420 storage = lazy_namespaces[LAZY_NAMESPACE_EXPUNGE]->storage;
421 dest_storage = lazy_namespaces[LAZY_NAMESPACE_DELETE_EXPUNGE]->storage;
422 (void)mailbox_move(storage, name, dest_storage, &destname);
423 return 0;
424 }
425
426 static void lazy_expunge_mail_storage_created(struct mail_storage *storage)
427 {
428 struct lazy_expunge_mail_storage *lstorage;
429
430 /* only maildir supported for now */
431 if (strcmp(storage->name, "maildir") != 0)
432 return;
433
434 lstorage = p_new(storage->pool, struct lazy_expunge_mail_storage, 1);
435 lstorage->super = storage->v;
436 storage->v.mailbox_open = lazy_expunge_mailbox_open;
437 storage->v.mailbox_delete = lazy_expunge_mailbox_delete;
438
439 if (!lazy_expunge_storage_module_id_set) {
440 lazy_expunge_storage_module_id = mail_storage_module_id++;
441 lazy_expunge_storage_module_id_set = TRUE;
442 }
443
444 array_idx_set(&storage->module_contexts,
445 lazy_expunge_storage_module_id, &lstorage);
446 }
447
448 static void lazy_expunge_hook_client_created(struct client **client)
449 {
450 struct lazy_expunge_mail_storage *lstorage;
451 const char *const *p;
452 int i;
453
454 if (lazy_expunge_next_hook_client_created != NULL)
455 lazy_expunge_next_hook_client_created(client);
456
457 /* FIXME: this works only as long as there's only one client. */
458 t_push();
459 p = t_strsplit(getenv("LAZY_EXPUNGE"), " ");
460 for (i = 0; i < LAZY_NAMESPACE_COUNT; i++, p++) {
461 const char *name = *p;
462
463 if (name == NULL)
464 i_fatal("lazy_expunge: Missing namespace #%d", i + 1);
465
466 lazy_namespaces[i] =
467 namespace_find_prefix((*client)->namespaces, name);
468 if (lazy_namespaces[i] == NULL)
469 i_fatal("lazy_expunge: Unknown namespace: '%s'", name);
470 if (strcmp(lazy_namespaces[i]->storage->name, "maildir") != 0) {
471 i_fatal("lazy_expunge: Namespace must be in maildir "
472 "format: %s", name);
473 }
474
475 /* we don't want to override these namespaces' expunge/delete
476 operations. */
477 lstorage = LAZY_EXPUNGE_CONTEXT(lazy_namespaces[i]->storage);
478 lazy_namespaces[i]->storage->v = lstorage->super;
479 }
480 t_pop();
481 }
482
483 void lazy_expunge_plugin_init(void)
484 {
485 if (getenv("LAZY_EXPUNGE") == NULL)
486 return;
487
488 lazy_expunge_next_hook_client_created = hook_client_created;
489 hook_client_created = lazy_expunge_hook_client_created;
490
491 lazy_expunge_next_hook_mail_storage_created =
492 hook_mail_storage_created;
493 hook_mail_storage_created = lazy_expunge_mail_storage_created;
494 }
495
496 void lazy_expunge_plugin_deinit(void)
497 {
498 if (lazy_expunge_storage_module_id_set) {
499 hook_client_created = lazy_expunge_hook_client_created;
500
501 hook_mail_storage_created =
502 lazy_expunge_next_hook_mail_storage_created;
503 }
504 }