Mercurial > dovecot > core-2.2
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(<->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, <); | |
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(<->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(<->expunge_box); | |
187 if (array_is_created(<->expunge_seqs)) | |
188 array_free(<->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(<->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 } |