1 /**
2 * @file
3 * Maildir local mailbox type
4 *
5 * @authors
6 * Copyright (C) 1996-2002,2007,2009 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2005 Thomas Roessler <roessler@does-not-exist.org>
8 * Copyright (C) 2010,2013 Michael R. Elkins <me@mutt.org>
9 * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
10 *
11 * @copyright
12 * This program is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free Software
14 * Foundation, either version 2 of the License, or (at your option) any later
15 * version.
16 *
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * details.
21 *
22 * You should have received a copy of the GNU General Public License along with
23 * this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 /**
27 * @page maildir_maildir Maildir local mailbox type
28 *
29 * Maildir local mailbox type
30 *
31 * Implementation: #MxMaildirOps
32 */
33
34 #include "config.h"
35 #include <dirent.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <inttypes.h>
39 #include <limits.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
47 #include <utime.h>
48 #include "private.h"
49 #include "mutt/lib.h"
50 #include "config/lib.h"
51 #include "email/lib.h"
52 #include "core/lib.h"
53 #include "lib.h"
54 #include "progress/lib.h"
55 #include "copy.h"
56 #include "edata.h"
57 #include "mdata.h"
58 #include "mdemail.h"
59 #include "monitor.h"
60 #include "mutt_globals.h"
61 #include "mx.h"
62 #ifdef USE_HCACHE
63 #include "hcache/lib.h"
64 #endif
65 #ifdef USE_NOTMUCH
66 #include "notmuch/lib.h"
67 #endif
68
69 struct Progress;
70
71 // Flags for maildir_mbox_check()
72 #define MMC_NO_DIRS 0 ///< No directories changed
73 #define MMC_NEW_DIR (1 << 0) ///< 'new' directory changed
74 #define MMC_CUR_DIR (1 << 1) ///< 'cur' directory changed
75
76 /**
77 * maildir_check_dir - Check for new mail / mail counts
78 * @param m Mailbox to check
79 * @param dir_name Path to Mailbox
80 * @param check_new if true, check for new mail
81 * @param check_stats if true, count total, new, and flagged messages
82 *
83 * Checks the specified maildir subdir (cur or new) for new mail or mail counts.
84 */
maildir_check_dir(struct Mailbox * m,const char * dir_name,bool check_new,bool check_stats)85 static void maildir_check_dir(struct Mailbox *m, const char *dir_name,
86 bool check_new, bool check_stats)
87 {
88 DIR *dirp = NULL;
89 struct dirent *de = NULL;
90 char *p = NULL;
91 struct stat st = { 0 };
92
93 struct Buffer *path = mutt_buffer_pool_get();
94 struct Buffer *msgpath = mutt_buffer_pool_get();
95 mutt_buffer_printf(path, "%s/%s", mailbox_path(m), dir_name);
96
97 /* when $mail_check_recent is set, if the new/ directory hasn't been modified since
98 * the user last exited the m, then we know there is no recent mail. */
99 const bool c_mail_check_recent =
100 cs_subset_bool(NeoMutt->sub, "mail_check_recent");
101 if (check_new && c_mail_check_recent)
102 {
103 if ((stat(mutt_buffer_string(path), &st) == 0) &&
104 (mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->last_visited) < 0))
105 {
106 check_new = false;
107 }
108 }
109
110 if (!(check_new || check_stats))
111 goto cleanup;
112
113 dirp = opendir(mutt_buffer_string(path));
114 if (!dirp)
115 {
116 m->type = MUTT_UNKNOWN;
117 goto cleanup;
118 }
119
120 while ((de = readdir(dirp)))
121 {
122 if (*de->d_name == '.')
123 continue;
124
125 p = strstr(de->d_name, ":2,");
126 if (p && strchr(p + 3, 'T'))
127 continue;
128
129 if (check_stats)
130 {
131 m->msg_count++;
132 if (p && strchr(p + 3, 'F'))
133 m->msg_flagged++;
134 }
135 if (!p || !strchr(p + 3, 'S'))
136 {
137 if (check_stats)
138 m->msg_unread++;
139 if (check_new)
140 {
141 if (c_mail_check_recent)
142 {
143 mutt_buffer_printf(msgpath, "%s/%s", mutt_buffer_string(path), de->d_name);
144 /* ensure this message was received since leaving this m */
145 if ((stat(mutt_buffer_string(msgpath), &st) == 0) &&
146 (mutt_file_stat_timespec_compare(&st, MUTT_STAT_CTIME, &m->last_visited) <= 0))
147 {
148 continue;
149 }
150 }
151 m->has_new = true;
152 check_new = false;
153 m->msg_new++;
154 if (!check_stats)
155 break;
156 }
157 }
158 }
159
160 closedir(dirp);
161
162 cleanup:
163 mutt_buffer_pool_release(&path);
164 mutt_buffer_pool_release(&msgpath);
165 }
166
167 /**
168 * ch_compare - qsort() callback to sort characters
169 * @param a First character to compare
170 * @param b Second character to compare
171 * @retval -1 a precedes b
172 * @retval 0 a and b are identical
173 * @retval 1 b precedes a
174 */
ch_compare(const void * a,const void * b)175 static int ch_compare(const void *a, const void *b)
176 {
177 return (int) (*((const char *) a) - *((const char *) b));
178 }
179
180 /**
181 * maildir_gen_flags - Generate the Maildir flags for an email
182 * @param dest Buffer for the result
183 * @param destlen Length of buffer
184 * @param e Email
185 */
maildir_gen_flags(char * dest,size_t destlen,struct Email * e)186 void maildir_gen_flags(char *dest, size_t destlen, struct Email *e)
187 {
188 *dest = '\0';
189
190 const char *flags = NULL;
191
192 struct MaildirEmailData *edata = maildir_edata_get(e);
193 if (edata)
194 flags = edata->maildir_flags;
195
196 /* The maildir specification requires that all files in the cur
197 * subdirectory have the :unique string appended, regardless of whether
198 * or not there are any flags. If .old is set, we know that this message
199 * will end up in the cur directory, so we include it in the following
200 * test even though there is no associated flag. */
201
202 if (e->flagged || e->replied || e->read || e->deleted || e->old || flags)
203 {
204 char tmp[1024];
205 snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", e->flagged ? "F" : "", e->replied ? "R" : "",
206 e->read ? "S" : "", e->deleted ? "T" : "", NONULL(flags));
207 if (flags)
208 qsort(tmp, strlen(tmp), 1, ch_compare);
209 snprintf(dest, destlen, ":2,%s", tmp);
210 }
211 }
212
213 /**
214 * maildir_commit_message - Commit a message to a maildir folder
215 * @param m Mailbox
216 * @param msg Message to commit
217 * @param e Email
218 * @retval 0 Success
219 * @retval -1 Failure
220 *
221 * msg->path contains the file name of a file in tmp/. We take the
222 * flags from this file's name.
223 *
224 * m is the mail folder we commit to.
225 *
226 * e is a header structure to which we write the message's new
227 * file name. This is used in the mh and maildir folder sync
228 * routines. When this routine is invoked from mx_msg_commit(),
229 * e is NULL.
230 *
231 * msg->path looks like this:
232 *
233 * tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags
234 *
235 * See also maildir_msg_open_new().
236 */
maildir_commit_message(struct Mailbox * m,struct Message * msg,struct Email * e)237 int maildir_commit_message(struct Mailbox *m, struct Message *msg, struct Email *e)
238 {
239 char subdir[4];
240 char suffix[16];
241 int rc = 0;
242
243 if (mutt_file_fsync_close(&msg->fp))
244 {
245 mutt_perror(_("Could not flush message to disk"));
246 return -1;
247 }
248
249 /* extract the subdir */
250 char *s = strrchr(msg->path, '/') + 1;
251 mutt_str_copy(subdir, s, 4);
252
253 /* extract the flags */
254 s = strchr(s, ':');
255 if (s)
256 mutt_str_copy(suffix, s, sizeof(suffix));
257 else
258 suffix[0] = '\0';
259
260 /* construct a new file name. */
261 struct Buffer *path = mutt_buffer_pool_get();
262 struct Buffer *full = mutt_buffer_pool_get();
263 while (true)
264 {
265 mutt_buffer_printf(path, "%s/%lld.R%" PRIu64 ".%s%s", subdir,
266 (long long) mutt_date_epoch(), mutt_rand64(),
267 NONULL(ShortHostname), suffix);
268 mutt_buffer_printf(full, "%s/%s", mailbox_path(m), mutt_buffer_string(path));
269
270 mutt_debug(LL_DEBUG2, "renaming %s to %s\n", msg->path, mutt_buffer_string(full));
271
272 if (mutt_file_safe_rename(msg->path, mutt_buffer_string(full)) == 0)
273 {
274 /* Adjust the mtime on the file to match the time at which this
275 * message was received. Currently this is only set when copying
276 * messages between mailboxes, so we test to ensure that it is
277 * actually set. */
278 if (msg->received)
279 {
280 struct utimbuf ut;
281 int rc_utime;
282
283 ut.actime = msg->received;
284 ut.modtime = msg->received;
285 do
286 {
287 rc_utime = utime(mutt_buffer_string(full), &ut);
288 } while ((rc_utime == -1) && (errno == EINTR));
289 if (rc_utime == -1)
290 {
291 mutt_perror(
292 _("maildir_commit_message(): unable to set time on file"));
293 rc = -1;
294 goto cleanup;
295 }
296 }
297
298 #ifdef USE_NOTMUCH
299 if (m->type == MUTT_NOTMUCH)
300 nm_update_filename(m, e->path, mutt_buffer_string(full), e);
301 #endif
302 if (e)
303 mutt_str_replace(&e->path, mutt_buffer_string(path));
304 mutt_str_replace(&msg->committed_path, mutt_buffer_string(full));
305 FREE(&msg->path);
306
307 goto cleanup;
308 }
309 else if (errno != EEXIST)
310 {
311 mutt_perror(mailbox_path(m));
312 rc = -1;
313 goto cleanup;
314 }
315 }
316
317 cleanup:
318 mutt_buffer_pool_release(&path);
319 mutt_buffer_pool_release(&full);
320
321 return rc;
322 }
323
324 /**
325 * maildir_rewrite_message - Sync a message in an MH folder
326 * @param m Mailbox
327 * @param msgno Index number
328 * @retval 0 Success
329 * @retval -1 Error
330 */
maildir_rewrite_message(struct Mailbox * m,int msgno)331 int maildir_rewrite_message(struct Mailbox *m, int msgno)
332 {
333 if (!m || !m->emails || (msgno >= m->msg_count))
334 return -1;
335
336 struct Email *e = m->emails[msgno];
337 if (!e)
338 return -1;
339
340 bool restore = true;
341
342 long old_body_offset = e->body->offset;
343 long old_body_length = e->body->length;
344 long old_hdr_lines = e->lines;
345
346 struct Message *src = mx_msg_open(m, msgno);
347 struct Message *dest = mx_msg_open_new(m, e, MUTT_MSG_NO_FLAGS);
348 if (!src || !dest)
349 return -1;
350
351 int rc = mutt_copy_message(dest->fp, e, src, MUTT_CM_UPDATE, CH_UPDATE | CH_UPDATE_LEN, 0);
352 if (rc == 0)
353 {
354 char oldpath[PATH_MAX];
355 char partpath[PATH_MAX];
356 snprintf(oldpath, sizeof(oldpath), "%s/%s", mailbox_path(m), e->path);
357 mutt_str_copy(partpath, e->path, sizeof(partpath));
358
359 rc = maildir_commit_message(m, dest, e);
360
361 if (rc == 0)
362 {
363 unlink(oldpath);
364 restore = false;
365 }
366 }
367 mx_msg_close(m, &src);
368 mx_msg_close(m, &dest);
369
370 if ((rc == -1) && restore)
371 {
372 e->body->offset = old_body_offset;
373 e->body->length = old_body_length;
374 e->lines = old_hdr_lines;
375 }
376
377 mutt_body_free(&e->body->parts);
378 return rc;
379 }
380
381 /**
382 * maildir_sync_message - Sync an email to a Maildir folder
383 * @param m Mailbox
384 * @param msgno Index number
385 * @retval 0 Success
386 * @retval -1 Error
387 */
maildir_sync_message(struct Mailbox * m,int msgno)388 int maildir_sync_message(struct Mailbox *m, int msgno)
389 {
390 if (!m || !m->emails || (msgno >= m->msg_count))
391 return -1;
392
393 struct Email *e = m->emails[msgno];
394 if (!e)
395 return -1;
396
397 struct Buffer *newpath = NULL;
398 struct Buffer *partpath = NULL;
399 struct Buffer *fullpath = NULL;
400 struct Buffer *oldpath = NULL;
401 char suffix[16];
402 int rc = 0;
403
404 /* TODO: why the e->env check? */
405 if (e->attach_del || (e->env && e->env->changed))
406 {
407 /* when doing attachment deletion/rethreading, fall back to the MH case. */
408 if (maildir_rewrite_message(m, msgno) != 0)
409 return -1;
410 /* TODO: why the env check? */
411 if (e->env)
412 e->env->changed = 0;
413 }
414 else
415 {
416 /* we just have to rename the file. */
417
418 char *p = strrchr(e->path, '/');
419 if (!p)
420 {
421 mutt_debug(LL_DEBUG1, "%s: unable to find subdir!\n", e->path);
422 return -1;
423 }
424 p++;
425 newpath = mutt_buffer_pool_get();
426 partpath = mutt_buffer_pool_get();
427 fullpath = mutt_buffer_pool_get();
428 oldpath = mutt_buffer_pool_get();
429
430 mutt_buffer_strcpy(newpath, p);
431
432 /* kill the previous flags */
433 p = strchr(newpath->data, ':');
434 if (p)
435 {
436 *p = '\0';
437 newpath->dptr = p; /* fix buffer up, just to be safe */
438 }
439
440 maildir_gen_flags(suffix, sizeof(suffix), e);
441
442 mutt_buffer_printf(partpath, "%s/%s%s", (e->read || e->old) ? "cur" : "new",
443 mutt_buffer_string(newpath), suffix);
444 mutt_buffer_printf(fullpath, "%s/%s", mailbox_path(m), mutt_buffer_string(partpath));
445 mutt_buffer_printf(oldpath, "%s/%s", mailbox_path(m), e->path);
446
447 if (mutt_str_equal(mutt_buffer_string(fullpath), mutt_buffer_string(oldpath)))
448 {
449 /* message hasn't really changed */
450 goto cleanup;
451 }
452
453 /* record that the message is possibly marked as trashed on disk */
454 e->trash = e->deleted;
455
456 if (rename(mutt_buffer_string(oldpath), mutt_buffer_string(fullpath)) != 0)
457 {
458 mutt_perror("rename");
459 rc = -1;
460 goto cleanup;
461 }
462 mutt_str_replace(&e->path, mutt_buffer_string(partpath));
463 }
464
465 cleanup:
466 mutt_buffer_pool_release(&newpath);
467 mutt_buffer_pool_release(&partpath);
468 mutt_buffer_pool_release(&fullpath);
469 mutt_buffer_pool_release(&oldpath);
470
471 return rc;
472 }
473
474 /**
475 * maildir_update_mtime - Update our record of the Maildir modification time
476 * @param m Mailbox
477 */
maildir_update_mtime(struct Mailbox * m)478 void maildir_update_mtime(struct Mailbox *m)
479 {
480 char buf[PATH_MAX];
481 struct stat st = { 0 };
482 struct MaildirMboxData *mdata = maildir_mdata_get(m);
483
484 snprintf(buf, sizeof(buf), "%s/%s", mailbox_path(m), "cur");
485 if (stat(buf, &st) == 0)
486 mutt_file_get_stat_timespec(&mdata->mtime_cur, &st, MUTT_STAT_MTIME);
487 snprintf(buf, sizeof(buf), "%s/%s", mailbox_path(m), "new");
488
489 if (stat(buf, &st) == 0)
490 mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME);
491 }
492
493 /**
494 * maildir_cmp_inode - Compare two Maildirs by inode number - Implements ::sort_t - @ingroup sort_api
495 */
maildir_cmp_inode(const void * a,const void * b)496 static int maildir_cmp_inode(const void *a, const void *b)
497 {
498 const struct MdEmail *ma = *(struct MdEmail **) a;
499 const struct MdEmail *mb = *(struct MdEmail **) b;
500
501 return ma->inode - mb->inode;
502 }
503
504 /**
505 * maildir_parse_dir - Read a Maildir mailbox
506 * @param[in] m Mailbox
507 * @param[out] mda Array for results
508 * @param[in] subdir Subdirectory, e.g. 'new'
509 * @param[in] progress Progress bar
510 * @retval 0 Success
511 * @retval -1 Error
512 * @retval -2 Aborted
513 */
maildir_parse_dir(struct Mailbox * m,struct MdEmailArray * mda,const char * subdir,struct Progress * progress)514 int maildir_parse_dir(struct Mailbox *m, struct MdEmailArray *mda,
515 const char *subdir, struct Progress *progress)
516 {
517 struct dirent *de = NULL;
518 int rc = 0;
519 bool is_old = false;
520 struct MdEmail *entry = NULL;
521 struct Email *e = NULL;
522
523 struct Buffer *buf = mutt_buffer_pool_get();
524
525 mutt_buffer_printf(buf, "%s/%s", mailbox_path(m), subdir);
526 const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old");
527 is_old = c_mark_old ? mutt_str_equal("cur", subdir) : false;
528
529 DIR *dirp = opendir(mutt_buffer_string(buf));
530 if (!dirp)
531 {
532 rc = -1;
533 goto cleanup;
534 }
535
536 while (((de = readdir(dirp))) && !SigInt)
537 {
538 if (*de->d_name == '.')
539 continue;
540
541 /* FOO - really ignore the return value? */
542 mutt_debug(LL_DEBUG2, "queueing %s\n", de->d_name);
543
544 e = email_new();
545 e->edata = maildir_edata_new();
546 e->edata_free = maildir_edata_free;
547
548 e->old = is_old;
549 maildir_parse_flags(e, de->d_name);
550
551 if (m->verbose && progress)
552 progress_update(progress, ARRAY_SIZE(mda) + 1, -1);
553
554 mutt_buffer_printf(buf, "%s/%s", subdir, de->d_name);
555 e->path = mutt_buffer_strdup(buf);
556
557 entry = maildir_entry_new();
558 entry->email = e;
559 entry->inode = de->d_ino;
560 ARRAY_ADD(mda, entry);
561 }
562
563 closedir(dirp);
564
565 if (SigInt)
566 {
567 SigInt = false;
568 return -2; /* action aborted */
569 }
570
571 ARRAY_SORT(mda, maildir_cmp_inode);
572
573 cleanup:
574 mutt_buffer_pool_release(&buf);
575
576 return rc;
577 }
578
579 /**
580 * maildir_hcache_keylen - Calculate the length of the Maildir path
581 * @param fn File name
582 * @retval num Length in bytes
583 *
584 * @note This length excludes the flags, which will vary
585 */
maildir_hcache_keylen(const char * fn)586 size_t maildir_hcache_keylen(const char *fn)
587 {
588 const char *p = strrchr(fn, ':');
589 return p ? (size_t) (p - fn) : mutt_str_len(fn);
590 }
591
592 /**
593 * maildir_delayed_parsing - This function does the second parsing pass
594 * @param[in] m Mailbox
595 * @param[out] mda Maildir array to parse
596 * @param[in] progress Progress bar
597 */
maildir_delayed_parsing(struct Mailbox * m,struct MdEmailArray * mda,struct Progress * progress)598 void maildir_delayed_parsing(struct Mailbox *m, struct MdEmailArray *mda,
599 struct Progress *progress)
600 {
601 char fn[PATH_MAX];
602
603 #ifdef USE_HCACHE
604 const char *const c_header_cache =
605 cs_subset_path(NeoMutt->sub, "header_cache");
606 struct HeaderCache *hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
607 #endif
608
609 struct MdEmail *md = NULL;
610 struct MdEmail **mdp = NULL;
611 ARRAY_FOREACH(mdp, mda)
612 {
613 md = *mdp;
614 if (!md || !md->email || md->header_parsed)
615 continue;
616
617 if (m->verbose && progress)
618 progress_update(progress, ARRAY_FOREACH_IDX, -1);
619
620 snprintf(fn, sizeof(fn), "%s/%s", mailbox_path(m), md->email->path);
621
622 #ifdef USE_HCACHE
623 struct stat st_lastchanged = { 0 };
624 int rc = 0;
625 const bool c_maildir_header_cache_verify =
626 cs_subset_bool(NeoMutt->sub, "maildir_header_cache_verify");
627 if (c_maildir_header_cache_verify)
628 {
629 rc = stat(fn, &st_lastchanged);
630 }
631
632 const char *key = md->email->path + 3;
633 size_t keylen = maildir_hcache_keylen(key);
634 struct HCacheEntry hce = mutt_hcache_fetch(hc, key, keylen, 0);
635
636 if (hce.email && (rc == 0) && (st_lastchanged.st_mtime <= hce.uidvalidity))
637 {
638 hce.email->edata = maildir_edata_new();
639 hce.email->edata_free = maildir_edata_free;
640 hce.email->old = md->email->old;
641 hce.email->path = mutt_str_dup(md->email->path);
642 email_free(&md->email);
643 md->email = hce.email;
644 maildir_parse_flags(md->email, fn);
645 }
646 else
647 #endif
648 {
649 if (maildir_parse_message(m->type, fn, md->email->old, md->email))
650 {
651 md->header_parsed = true;
652 #ifdef USE_HCACHE
653 key = md->email->path + 3;
654 keylen = maildir_hcache_keylen(key);
655 mutt_hcache_store(hc, key, keylen, md->email, 0);
656 #endif
657 }
658 else
659 email_free(&md->email);
660 }
661 }
662 #ifdef USE_HCACHE
663 mutt_hcache_close(hc);
664 #endif
665 }
666
667 /**
668 * maildir_read_dir - Read a Maildir style mailbox
669 * @param m Mailbox
670 * @param subdir Subdir of the maildir mailbox to read from
671 * @retval 0 Success
672 * @retval -1 Failure
673 */
maildir_read_dir(struct Mailbox * m,const char * subdir)674 int maildir_read_dir(struct Mailbox *m, const char *subdir)
675 {
676 if (!m)
677 return -1;
678
679 struct Progress *progress = NULL;
680
681 if (m->verbose)
682 {
683 char msg[PATH_MAX];
684 snprintf(msg, sizeof(msg), _("Scanning %s..."), mailbox_path(m));
685 progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
686 }
687
688 struct MaildirMboxData *mdata = maildir_mdata_get(m);
689 if (!mdata)
690 {
691 mdata = maildir_mdata_new();
692 m->mdata = mdata;
693 m->mdata_free = maildir_mdata_free;
694 }
695
696 struct MdEmailArray mda = ARRAY_HEAD_INITIALIZER;
697 int rc = maildir_parse_dir(m, &mda, subdir, progress);
698 progress_free(&progress);
699 if (rc < 0)
700 return -1;
701
702 if (m->verbose)
703 {
704 char msg[PATH_MAX];
705 snprintf(msg, sizeof(msg), _("Reading %s..."), mailbox_path(m));
706 progress = progress_new(msg, MUTT_PROGRESS_READ, ARRAY_SIZE(&mda));
707 }
708 maildir_delayed_parsing(m, &mda, progress);
709 progress_free(&progress);
710
711 maildir_move_to_mailbox(m, &mda);
712
713 if (!mdata->mh_umask)
714 mdata->mh_umask = mh_umask(m);
715
716 return 0;
717 }
718
719 /**
720 * maildir_canon_filename - Generate the canonical filename for a Maildir folder
721 * @param dest Buffer for the result
722 * @param src Buffer containing source filename
723 *
724 * @note maildir filename is defined as: \<base filename\>:2,\<flags\>
725 * but \<base filename\> may contain additional comma separated
726 * fields.
727 */
maildir_canon_filename(struct Buffer * dest,const char * src)728 void maildir_canon_filename(struct Buffer *dest, const char *src)
729 {
730 if (!dest || !src)
731 return;
732
733 char *t = strrchr(src, '/');
734 if (t)
735 src = t + 1;
736
737 mutt_buffer_strcpy(dest, src);
738 char *u = strpbrk(dest->data, ",:");
739 if (u)
740 {
741 *u = '\0';
742 dest->dptr = u;
743 }
744 }
745
746 /**
747 * maildir_open_find_message_dir - Find a message in a maildir folder
748 * @param[in] folder Base folder
749 * @param[in] unique Unique part of filename
750 * @param[in] subfolder Subfolder to search, e.g. 'cur'
751 * @param[out] newname File's new name
752 * @retval ptr File handle
753 *
754 * These functions try to find a message in a maildir folder when it
755 * has moved under our feet. Note that this code is rather expensive, but
756 * then again, it's called rarely.
757 */
maildir_open_find_message_dir(const char * folder,const char * unique,const char * subfolder,char ** newname)758 static FILE *maildir_open_find_message_dir(const char *folder, const char *unique,
759 const char *subfolder, char **newname)
760 {
761 struct Buffer *dir = mutt_buffer_pool_get();
762 struct Buffer *tunique = mutt_buffer_pool_get();
763 struct Buffer *fname = mutt_buffer_pool_get();
764
765 struct dirent *de = NULL;
766
767 FILE *fp = NULL;
768 int oe = ENOENT;
769
770 mutt_buffer_printf(dir, "%s/%s", folder, subfolder);
771
772 DIR *dp = opendir(mutt_buffer_string(dir));
773 if (!dp)
774 {
775 errno = ENOENT;
776 goto cleanup;
777 }
778
779 while ((de = readdir(dp)))
780 {
781 maildir_canon_filename(tunique, de->d_name);
782
783 if (mutt_str_equal(mutt_buffer_string(tunique), unique))
784 {
785 mutt_buffer_printf(fname, "%s/%s/%s", folder, subfolder, de->d_name);
786 fp = fopen(mutt_buffer_string(fname), "r");
787 oe = errno;
788 break;
789 }
790 }
791
792 closedir(dp);
793
794 if (newname && fp)
795 *newname = mutt_buffer_strdup(fname);
796
797 errno = oe;
798
799 cleanup:
800 mutt_buffer_pool_release(&dir);
801 mutt_buffer_pool_release(&tunique);
802 mutt_buffer_pool_release(&fname);
803
804 return fp;
805 }
806
807 /**
808 * maildir_parse_flags - Parse Maildir file flags
809 * @param e Email
810 * @param path Path to email file
811 */
maildir_parse_flags(struct Email * e,const char * path)812 void maildir_parse_flags(struct Email *e, const char *path)
813 {
814 char *q = NULL;
815
816 e->flagged = false;
817 e->read = false;
818 e->replied = false;
819
820 struct MaildirEmailData *edata = maildir_edata_get(e);
821
822 char *p = strrchr(path, ':');
823 if (p && mutt_str_startswith(p + 1, "2,"))
824 {
825 p += 3;
826
827 mutt_str_replace(&edata->maildir_flags, p);
828 q = edata->maildir_flags;
829
830 while (*p)
831 {
832 switch (*p)
833 {
834 case 'F':
835 e->flagged = true;
836 break;
837
838 case 'R': /* replied */
839 e->replied = true;
840 break;
841
842 case 'S': /* seen */
843 e->read = true;
844 break;
845
846 case 'T': /* trashed */
847 {
848 const bool c_flag_safe = cs_subset_bool(NeoMutt->sub, "flag_safe");
849 if (!e->flagged || !c_flag_safe)
850 {
851 e->trash = true;
852 e->deleted = true;
853 }
854 break;
855 }
856
857 default:
858 *q++ = *p;
859 break;
860 }
861 p++;
862 }
863 }
864
865 if (q == edata->maildir_flags)
866 FREE(&edata->maildir_flags);
867 else if (q)
868 *q = '\0';
869 }
870
871 /**
872 * maildir_parse_stream - Parse a Maildir message
873 * @param type Mailbox type, e.g. #MUTT_MAILDIR
874 * @param fp Message file handle
875 * @param fname Message filename
876 * @param is_old true, if the email is old (read)
877 * @param e Email
878 * @retval ptr Populated Email
879 * @retval NULL on error
880 *
881 * Actually parse a maildir message. This may also be used to fill
882 * out a fake header structure generated by lazy maildir parsing.
883 */
maildir_parse_stream(enum MailboxType type,FILE * fp,const char * fname,bool is_old,struct Email * e)884 struct Email *maildir_parse_stream(enum MailboxType type, FILE *fp,
885 const char *fname, bool is_old, struct Email *e)
886 {
887 const long size = mutt_file_get_size_fp(fp);
888 if (size == 0)
889 {
890 return NULL;
891 }
892
893 if (!e)
894 {
895 e = email_new();
896 e->edata = maildir_edata_new();
897 e->edata_free = maildir_edata_free;
898 }
899 e->env = mutt_rfc822_read_header(fp, e, false, false);
900
901 if (!e->received)
902 e->received = e->date_sent;
903
904 /* always update the length since we have fresh information available. */
905 e->body->length = size - e->body->offset;
906
907 e->index = -1;
908
909 if (type == MUTT_MAILDIR)
910 {
911 /* maildir stores its flags in the filename, so ignore the
912 * flags in the header of the message */
913
914 e->old = is_old;
915 maildir_parse_flags(e, fname);
916 }
917 return e;
918 }
919
920 /**
921 * maildir_parse_message - Actually parse a maildir message
922 * @param type Mailbox type, e.g. #MUTT_MAILDIR
923 * @param fname Message filename
924 * @param is_old true, if the email is old (read)
925 * @param e Email to populate (OPTIONAL)
926 * @retval ptr Populated Email
927 *
928 * This may also be used to fill out a fake header structure generated by lazy
929 * maildir parsing.
930 */
maildir_parse_message(enum MailboxType type,const char * fname,bool is_old,struct Email * e)931 struct Email *maildir_parse_message(enum MailboxType type, const char *fname,
932 bool is_old, struct Email *e)
933 {
934 FILE *fp = fopen(fname, "r");
935 if (!fp)
936 return NULL;
937
938 struct Email *e_res = maildir_parse_stream(type, fp, fname, is_old, e);
939 mutt_file_fclose(&fp);
940 return e_res;
941 }
942
943 /**
944 * maildir_sync_mailbox_message - Save changes to the mailbox
945 * @param m Mailbox
946 * @param msgno Index number
947 * @param hc Header cache handle
948 * @retval true Success
949 * @retval false Error
950 */
maildir_sync_mailbox_message(struct Mailbox * m,int msgno,struct HeaderCache * hc)951 bool maildir_sync_mailbox_message(struct Mailbox *m, int msgno, struct HeaderCache *hc)
952 {
953 struct Email *e = m->emails[msgno];
954 if (!e)
955 return false;
956
957 const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
958 if (e->deleted && !c_maildir_trash)
959 {
960 char path[PATH_MAX];
961 snprintf(path, sizeof(path), "%s/%s", mailbox_path(m), e->path);
962 #ifdef USE_HCACHE
963 if (hc)
964 {
965 const char *key = e->path + 3;
966 size_t keylen = maildir_hcache_keylen(key);
967 mutt_hcache_delete_record(hc, key, keylen);
968 }
969 #endif
970 unlink(path);
971 }
972 else if (e->changed || e->attach_del ||
973 ((c_maildir_trash || e->trash) && (e->deleted != e->trash)))
974 {
975 if (maildir_sync_message(m, msgno) == -1)
976 return false;
977 }
978
979 #ifdef USE_HCACHE
980 if (hc && e->changed)
981 {
982 const char *key = e->path + 3;
983 size_t keylen = maildir_hcache_keylen(key);
984 mutt_hcache_store(hc, key, keylen, e, 0);
985 }
986 #endif
987
988 return true;
989 }
990
991 /**
992 * maildir_open_find_message - Find a new
993 * @param[in] folder Maildir path
994 * @param[in] msg Email path
995 * @param[out] newname New name, if it has moved
996 * @retval ptr File handle
997 */
maildir_open_find_message(const char * folder,const char * msg,char ** newname)998 FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname)
999 {
1000 static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */
1001
1002 struct Buffer *unique = mutt_buffer_pool_get();
1003 maildir_canon_filename(unique, msg);
1004
1005 FILE *fp = maildir_open_find_message_dir(folder, mutt_buffer_string(unique),
1006 (new_hits > cur_hits) ? "new" : "cur", newname);
1007 if (fp || (errno != ENOENT))
1008 {
1009 if ((new_hits < UINT_MAX) && (cur_hits < UINT_MAX))
1010 {
1011 new_hits += ((new_hits > cur_hits) ? 1 : 0);
1012 cur_hits += ((new_hits > cur_hits) ? 0 : 1);
1013 }
1014
1015 goto cleanup;
1016 }
1017 fp = maildir_open_find_message_dir(folder, mutt_buffer_string(unique),
1018 (new_hits > cur_hits) ? "cur" : "new", newname);
1019 if (fp || (errno != ENOENT))
1020 {
1021 if ((new_hits < UINT_MAX) && (cur_hits < UINT_MAX))
1022 {
1023 new_hits += ((new_hits > cur_hits) ? 0 : 1);
1024 cur_hits += ((new_hits > cur_hits) ? 1 : 0);
1025 }
1026
1027 goto cleanup;
1028 }
1029
1030 fp = NULL;
1031
1032 cleanup:
1033 mutt_buffer_pool_release(&unique);
1034
1035 return fp;
1036 }
1037
1038 /**
1039 * maildir_check_empty - Is the mailbox empty
1040 * @param path Mailbox to check
1041 * @retval 1 Mailbox is empty
1042 * @retval 0 Mailbox contains mail
1043 * @retval -1 Error
1044 */
maildir_check_empty(const char * path)1045 int maildir_check_empty(const char *path)
1046 {
1047 DIR *dp = NULL;
1048 struct dirent *de = NULL;
1049 int rc = 1; /* assume empty until we find a message */
1050 char realpath[PATH_MAX];
1051 int iter = 0;
1052
1053 /* Strategy here is to look for any file not beginning with a period */
1054
1055 do
1056 {
1057 /* we do "cur" on the first iteration since it's more likely that we'll
1058 * find old messages without having to scan both subdirs */
1059 snprintf(realpath, sizeof(realpath), "%s/%s", path, (iter == 0) ? "cur" : "new");
1060 dp = opendir(realpath);
1061 if (!dp)
1062 return -1;
1063 while ((de = readdir(dp)))
1064 {
1065 if (*de->d_name != '.')
1066 {
1067 rc = 0;
1068 break;
1069 }
1070 }
1071 closedir(dp);
1072 iter++;
1073 } while (rc && iter < 2);
1074
1075 return rc;
1076 }
1077
1078 /**
1079 * maildir_ac_owns_path - Check whether an Account own a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path
1080 */
maildir_ac_owns_path(struct Account * a,const char * path)1081 bool maildir_ac_owns_path(struct Account *a, const char *path)
1082 {
1083 return true;
1084 }
1085
1086 /**
1087 * maildir_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add
1088 */
maildir_ac_add(struct Account * a,struct Mailbox * m)1089 bool maildir_ac_add(struct Account *a, struct Mailbox *m)
1090 {
1091 return true;
1092 }
1093
1094 /**
1095 * maildir_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open
1096 */
maildir_mbox_open(struct Mailbox * m)1097 static enum MxOpenReturns maildir_mbox_open(struct Mailbox *m)
1098 {
1099 /* maildir looks sort of like MH, except that there are two subdirectories
1100 * of the main folder path from which to read messages */
1101 if ((maildir_read_dir(m, "new") == -1) || (maildir_read_dir(m, "cur") == -1))
1102 return MX_OPEN_ERROR;
1103
1104 return MX_OPEN_OK;
1105 }
1106
1107 /**
1108 * maildir_mbox_open_append - Open a Mailbox for appending - Implements MxOps::mbox_open_append() - @ingroup mx_mbox_open_append
1109 */
maildir_mbox_open_append(struct Mailbox * m,OpenMailboxFlags flags)1110 static bool maildir_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
1111 {
1112 if (!(flags & (MUTT_APPEND | MUTT_APPENDNEW | MUTT_NEWFOLDER)))
1113 {
1114 return true;
1115 }
1116
1117 errno = 0;
1118 if ((mutt_file_mkdir(mailbox_path(m), S_IRWXU) != 0) && (errno != EEXIST))
1119 {
1120 mutt_perror(mailbox_path(m));
1121 return false;
1122 }
1123
1124 char tmp[PATH_MAX];
1125 snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1126 errno = 0;
1127 if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1128 {
1129 mutt_perror(tmp);
1130 rmdir(mailbox_path(m));
1131 return false;
1132 }
1133
1134 snprintf(tmp, sizeof(tmp), "%s/new", mailbox_path(m));
1135 errno = 0;
1136 if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1137 {
1138 mutt_perror(tmp);
1139 snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1140 rmdir(tmp);
1141 rmdir(mailbox_path(m));
1142 return false;
1143 }
1144
1145 snprintf(tmp, sizeof(tmp), "%s/tmp", mailbox_path(m));
1146 errno = 0;
1147 if ((mkdir(tmp, S_IRWXU) != 0) && (errno != EEXIST))
1148 {
1149 mutt_perror(tmp);
1150 snprintf(tmp, sizeof(tmp), "%s/cur", mailbox_path(m));
1151 rmdir(tmp);
1152 snprintf(tmp, sizeof(tmp), "%s/new", mailbox_path(m));
1153 rmdir(tmp);
1154 rmdir(mailbox_path(m));
1155 return false;
1156 }
1157
1158 return true;
1159 }
1160
1161 /**
1162 * maildir_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check
1163 *
1164 * This function handles arrival of new mail and reopening of maildir folders.
1165 * The basic idea here is we check to see if either the new or cur
1166 * subdirectories have changed, and if so, we scan them for the list of files.
1167 * We check for newly added messages, and then merge the flags messages we
1168 * already knew about. We don't treat either subdirectory differently, as mail
1169 * could be copied directly into the cur directory from another agent.
1170 */
maildir_mbox_check(struct Mailbox * m)1171 enum MxStatus maildir_mbox_check(struct Mailbox *m)
1172 {
1173 struct stat st_new = { 0 }; /* status of the "new" subdirectory */
1174 struct stat st_cur = { 0 }; /* status of the "cur" subdirectory */
1175 int changed = MMC_NO_DIRS; /* which subdirectories have changed */
1176 bool occult = false; /* messages were removed from the mailbox */
1177 int num_new = 0; /* number of new messages added to the mailbox */
1178 bool flags_changed = false; /* message flags were changed in the mailbox */
1179 struct HashTable *fnames = NULL; /* hash table for quickly looking up the base filename
1180 for a maildir message */
1181 struct MaildirMboxData *mdata = maildir_mdata_get(m);
1182
1183 /* XXX seems like this check belongs in mx_mbox_check() rather than here. */
1184 const bool c_check_new = cs_subset_bool(NeoMutt->sub, "check_new");
1185 if (!c_check_new)
1186 return MX_STATUS_OK;
1187
1188 struct Buffer *buf = mutt_buffer_pool_get();
1189 mutt_buffer_printf(buf, "%s/new", mailbox_path(m));
1190 if (stat(mutt_buffer_string(buf), &st_new) == -1)
1191 {
1192 mutt_buffer_pool_release(&buf);
1193 return MX_STATUS_ERROR;
1194 }
1195
1196 mutt_buffer_printf(buf, "%s/cur", mailbox_path(m));
1197 if (stat(mutt_buffer_string(buf), &st_cur) == -1)
1198 {
1199 mutt_buffer_pool_release(&buf);
1200 return MX_STATUS_ERROR;
1201 }
1202
1203 /* determine which subdirectories need to be scanned */
1204 if (mutt_file_stat_timespec_compare(&st_new, MUTT_STAT_MTIME, &m->mtime) > 0)
1205 changed = MMC_NEW_DIR;
1206 if (mutt_file_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &mdata->mtime_cur) > 0)
1207 changed |= MMC_CUR_DIR;
1208
1209 if (changed == MMC_NO_DIRS)
1210 {
1211 mutt_buffer_pool_release(&buf);
1212 return MX_STATUS_OK; /* nothing to do */
1213 }
1214
1215 /* Update the modification times on the mailbox.
1216 *
1217 * The monitor code notices changes in the open mailbox too quickly.
1218 * In practice, this sometimes leads to all the new messages not being
1219 * noticed during the SAME group of mtime stat updates. To work around
1220 * the problem, don't update the stat times for a monitor caused check. */
1221 #ifdef USE_INOTIFY
1222 if (MonitorContextChanged)
1223 MonitorContextChanged = false;
1224 else
1225 #endif
1226 {
1227 mutt_file_get_stat_timespec(&mdata->mtime_cur, &st_cur, MUTT_STAT_MTIME);
1228 mutt_file_get_stat_timespec(&m->mtime, &st_new, MUTT_STAT_MTIME);
1229 }
1230
1231 /* do a fast scan of just the filenames in
1232 * the subdirectories that have changed. */
1233 struct MdEmailArray mda = ARRAY_HEAD_INITIALIZER;
1234 if (changed & MMC_NEW_DIR)
1235 maildir_parse_dir(m, &mda, "new", NULL);
1236 if (changed & MMC_CUR_DIR)
1237 maildir_parse_dir(m, &mda, "cur", NULL);
1238
1239 /* we create a hash table keyed off the canonical (sans flags) filename
1240 * of each message we scanned. This is used in the loop over the
1241 * existing messages below to do some correlation. */
1242 fnames = mutt_hash_new(ARRAY_SIZE(&mda), MUTT_HASH_NO_FLAGS);
1243
1244 struct MdEmail *md = NULL;
1245 struct MdEmail **mdp = NULL;
1246 ARRAY_FOREACH(mdp, &mda)
1247 {
1248 md = *mdp;
1249 maildir_canon_filename(buf, md->email->path);
1250 md->canon_fname = mutt_buffer_strdup(buf);
1251 mutt_hash_insert(fnames, md->canon_fname, md);
1252 }
1253
1254 /* check for modifications and adjust flags */
1255 for (int i = 0; i < m->msg_count; i++)
1256 {
1257 struct Email *e = m->emails[i];
1258 if (!e)
1259 break;
1260
1261 e->active = false;
1262 maildir_canon_filename(buf, e->path);
1263 md = mutt_hash_find(fnames, mutt_buffer_string(buf));
1264 if (md && md->email)
1265 {
1266 /* message already exists, merge flags */
1267 e->active = true;
1268
1269 /* check to see if the message has moved to a different
1270 * subdirectory. If so, update the associated filename. */
1271 if (!mutt_str_equal(e->path, md->email->path))
1272 mutt_str_replace(&e->path, md->email->path);
1273
1274 /* if the user hasn't modified the flags on this message, update
1275 * the flags we just detected. */
1276 if (!e->changed)
1277 if (maildir_update_flags(m, e, md->email))
1278 flags_changed = true;
1279
1280 if (e->deleted == e->trash)
1281 {
1282 if (e->deleted != md->email->deleted)
1283 {
1284 e->deleted = md->email->deleted;
1285 flags_changed = true;
1286 }
1287 }
1288 e->trash = md->email->trash;
1289
1290 /* this is a duplicate of an existing email, so remove it */
1291 email_free(&md->email);
1292 }
1293 /* This message was not in the list of messages we just scanned.
1294 * Check to see if we have enough information to know if the
1295 * message has disappeared out from underneath us. */
1296 else if (((changed & MMC_NEW_DIR) && mutt_strn_equal(e->path, "new/", 4)) ||
1297 ((changed & MMC_CUR_DIR) && mutt_strn_equal(e->path, "cur/", 4)))
1298 {
1299 /* This message disappeared, so we need to simulate a "reopen"
1300 * event. We know it disappeared because we just scanned the
1301 * subdirectory it used to reside in. */
1302 occult = true;
1303 e->deleted = true;
1304 e->purge = true;
1305 }
1306 else
1307 {
1308 /* This message resides in a subdirectory which was not
1309 * modified, so we assume that it is still present and
1310 * unchanged. */
1311 e->active = true;
1312 }
1313 }
1314
1315 /* destroy the file name hash */
1316 mutt_hash_free(&fnames);
1317
1318 /* If we didn't just get new mail, update the tables. */
1319 if (occult)
1320 mailbox_changed(m, NT_MAILBOX_RESORT);
1321
1322 /* do any delayed parsing we need to do. */
1323 maildir_delayed_parsing(m, &mda, NULL);
1324
1325 /* Incorporate new messages */
1326 num_new = maildir_move_to_mailbox(m, &mda);
1327 if (num_new > 0)
1328 {
1329 mailbox_changed(m, NT_MAILBOX_INVALID);
1330 m->changed = true;
1331 }
1332
1333 mutt_buffer_pool_release(&buf);
1334
1335 ARRAY_FREE(&mda);
1336 if (occult)
1337 return MX_STATUS_REOPENED;
1338 if (num_new > 0)
1339 return MX_STATUS_NEW_MAIL;
1340 if (flags_changed)
1341 return MX_STATUS_FLAGS;
1342 return MX_STATUS_OK;
1343 }
1344
1345 /**
1346 * maildir_mbox_check_stats - Check the Mailbox statistics - Implements MxOps::mbox_check_stats() - @ingroup mx_mbox_check_stats
1347 */
maildir_mbox_check_stats(struct Mailbox * m,uint8_t flags)1348 static enum MxStatus maildir_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1349 {
1350 bool check_stats = flags;
1351 bool check_new = true;
1352
1353 if (check_stats)
1354 {
1355 m->msg_count = 0;
1356 m->msg_unread = 0;
1357 m->msg_flagged = 0;
1358 m->msg_new = 0;
1359 }
1360
1361 maildir_check_dir(m, "new", check_new, check_stats);
1362
1363 const bool c_maildir_check_cur =
1364 cs_subset_bool(NeoMutt->sub, "maildir_check_cur");
1365 check_new = !m->has_new && c_maildir_check_cur;
1366 if (check_new || check_stats)
1367 maildir_check_dir(m, "cur", check_new, check_stats);
1368
1369 return m->msg_new ? MX_STATUS_NEW_MAIL : MX_STATUS_OK;
1370 }
1371
1372 /**
1373 * maildir_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync
1374 * @retval enum #MxStatus
1375 *
1376 * @note The flag retvals come from a call to a backend sync function
1377 */
maildir_mbox_sync(struct Mailbox * m)1378 enum MxStatus maildir_mbox_sync(struct Mailbox *m)
1379 {
1380 enum MxStatus check = maildir_mbox_check(m);
1381 if (check == MX_STATUS_ERROR)
1382 return check;
1383
1384 struct HeaderCache *hc = NULL;
1385 #ifdef USE_HCACHE
1386 const char *const c_header_cache =
1387 cs_subset_path(NeoMutt->sub, "header_cache");
1388 if (m->type == MUTT_MAILDIR)
1389 hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
1390 #endif
1391
1392 struct Progress *progress = NULL;
1393 if (m->verbose)
1394 {
1395 char msg[PATH_MAX];
1396 snprintf(msg, sizeof(msg), _("Writing %s..."), mailbox_path(m));
1397 progress = progress_new(msg, MUTT_PROGRESS_WRITE, m->msg_count);
1398 }
1399
1400 for (int i = 0; i < m->msg_count; i++)
1401 {
1402 if (m->verbose)
1403 progress_update(progress, i, -1);
1404
1405 if (!maildir_sync_mailbox_message(m, i, hc))
1406 {
1407 progress_free(&progress);
1408 goto err;
1409 }
1410 }
1411 progress_free(&progress);
1412
1413 #ifdef USE_HCACHE
1414 if (m->type == MUTT_MAILDIR)
1415 mutt_hcache_close(hc);
1416 #endif
1417
1418 /* XXX race condition? */
1419
1420 maildir_update_mtime(m);
1421
1422 /* adjust indices */
1423
1424 if (m->msg_deleted)
1425 {
1426 for (int i = 0, j = 0; i < m->msg_count; i++)
1427 {
1428 struct Email *e = m->emails[i];
1429 if (!e)
1430 break;
1431
1432 const bool c_maildir_trash =
1433 cs_subset_bool(NeoMutt->sub, "maildir_trash");
1434 if (!e->deleted || c_maildir_trash)
1435 e->index = j++;
1436 }
1437 }
1438
1439 return check;
1440
1441 err:
1442 #ifdef USE_HCACHE
1443 if (m->type == MUTT_MAILDIR)
1444 mutt_hcache_close(hc);
1445 #endif
1446 return MX_STATUS_ERROR;
1447 }
1448
1449 /**
1450 * maildir_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close
1451 * @retval MX_STATUS_OK Always
1452 */
maildir_mbox_close(struct Mailbox * m)1453 enum MxStatus maildir_mbox_close(struct Mailbox *m)
1454 {
1455 return MX_STATUS_OK;
1456 }
1457
1458 /**
1459 * maildir_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
1460 */
maildir_msg_open(struct Mailbox * m,struct Message * msg,int msgno)1461 static bool maildir_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
1462 {
1463 struct Email *e = m->emails[msgno];
1464 if (!e)
1465 return false;
1466
1467 char path[PATH_MAX];
1468
1469 snprintf(path, sizeof(path), "%s/%s", mailbox_path(m), e->path);
1470
1471 msg->fp = fopen(path, "r");
1472 if (!msg->fp && (errno == ENOENT))
1473 msg->fp = maildir_open_find_message(mailbox_path(m), e->path, NULL);
1474
1475 if (!msg->fp)
1476 {
1477 mutt_perror(path);
1478 mutt_debug(LL_DEBUG1, "fopen: %s: %s (errno %d)\n", path, strerror(errno), errno);
1479 return false;
1480 }
1481
1482 return true;
1483 }
1484
1485 /**
1486 * maildir_msg_open_new - Open a new message in a Mailbox - Implements MxOps::msg_open_new() - @ingroup mx_msg_open_new
1487 *
1488 * Open a new (temporary) message in a maildir folder.
1489 *
1490 * @note This uses _almost_ the maildir file name format,
1491 * but with a {cur,new} prefix.
1492 */
maildir_msg_open_new(struct Mailbox * m,struct Message * msg,const struct Email * e)1493 bool maildir_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
1494 {
1495 int fd;
1496 char path[PATH_MAX];
1497 char suffix[16];
1498 char subdir[16];
1499
1500 if (e)
1501 {
1502 struct Email tmp = *e;
1503 tmp.deleted = false;
1504 tmp.edata = NULL;
1505 maildir_gen_flags(suffix, sizeof(suffix), &tmp);
1506 }
1507 else
1508 *suffix = '\0';
1509
1510 if (e && (e->read || e->old))
1511 mutt_str_copy(subdir, "cur", sizeof(subdir));
1512 else
1513 mutt_str_copy(subdir, "new", sizeof(subdir));
1514
1515 mode_t omask = umask(mh_umask(m));
1516 while (true)
1517 {
1518 snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s",
1519 mailbox_path(m), subdir, (long long) mutt_date_epoch(),
1520 mutt_rand64(), NONULL(ShortHostname), suffix);
1521
1522 mutt_debug(LL_DEBUG2, "Trying %s\n", path);
1523
1524 fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666);
1525 if (fd == -1)
1526 {
1527 if (errno != EEXIST)
1528 {
1529 umask(omask);
1530 mutt_perror(path);
1531 return false;
1532 }
1533 }
1534 else
1535 {
1536 mutt_debug(LL_DEBUG2, "Success\n");
1537 msg->path = mutt_str_dup(path);
1538 break;
1539 }
1540 }
1541 umask(omask);
1542
1543 msg->fp = fdopen(fd, "w");
1544 if (!msg->fp)
1545 {
1546 FREE(&msg->path);
1547 close(fd);
1548 unlink(path);
1549 return false;
1550 }
1551
1552 return true;
1553 }
1554
1555 /**
1556 * maildir_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
1557 */
maildir_msg_commit(struct Mailbox * m,struct Message * msg)1558 static int maildir_msg_commit(struct Mailbox *m, struct Message *msg)
1559 {
1560 return maildir_commit_message(m, msg, NULL);
1561 }
1562
1563 /**
1564 * maildir_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
1565 *
1566 * @note May also return EOF Failure, see errno
1567 */
maildir_msg_close(struct Mailbox * m,struct Message * msg)1568 int maildir_msg_close(struct Mailbox *m, struct Message *msg)
1569 {
1570 return mutt_file_fclose(&msg->fp);
1571 }
1572
1573 /**
1574 * maildir_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
1575 */
maildir_msg_save_hcache(struct Mailbox * m,struct Email * e)1576 static int maildir_msg_save_hcache(struct Mailbox *m, struct Email *e)
1577 {
1578 int rc = 0;
1579 #ifdef USE_HCACHE
1580 const char *const c_header_cache =
1581 cs_subset_path(NeoMutt->sub, "header_cache");
1582 struct HeaderCache *hc = mutt_hcache_open(c_header_cache, mailbox_path(m), NULL);
1583 char *key = e->path + 3;
1584 int keylen = maildir_hcache_keylen(key);
1585 rc = mutt_hcache_store(hc, key, keylen, e, 0);
1586 mutt_hcache_close(hc);
1587 #endif
1588 return rc;
1589 }
1590
1591 /**
1592 * maildir_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon
1593 */
maildir_path_canon(char * buf,size_t buflen)1594 int maildir_path_canon(char *buf, size_t buflen)
1595 {
1596 mutt_path_canon(buf, buflen, HomeDir, true);
1597 return 0;
1598 }
1599
1600 /**
1601 * maildir_path_parent - Find the parent of a Mailbox path - Implements MxOps::path_parent() - @ingroup mx_path_parent
1602 */
maildir_path_parent(char * buf,size_t buflen)1603 int maildir_path_parent(char *buf, size_t buflen)
1604 {
1605 if (mutt_path_parent(buf))
1606 return 0;
1607
1608 if (buf[0] == '~')
1609 mutt_path_canon(buf, buflen, HomeDir, true);
1610
1611 if (mutt_path_parent(buf))
1612 return 0;
1613
1614 return -1;
1615 }
1616
1617 /**
1618 * maildir_path_pretty - Abbreviate a Mailbox path - Implements MxOps::path_pretty() - @ingroup mx_path_pretty
1619 */
maildir_path_pretty(char * buf,size_t buflen,const char * folder)1620 int maildir_path_pretty(char *buf, size_t buflen, const char *folder)
1621 {
1622 if (mutt_path_abbr_folder(buf, folder))
1623 return 0;
1624
1625 if (mutt_path_pretty(buf, buflen, HomeDir, false))
1626 return 0;
1627
1628 return -1;
1629 }
1630
1631 /**
1632 * maildir_path_probe - Is this a Maildir Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe
1633 */
maildir_path_probe(const char * path,const struct stat * st)1634 static enum MailboxType maildir_path_probe(const char *path, const struct stat *st)
1635 {
1636 if (!st || !S_ISDIR(st->st_mode))
1637 return MUTT_UNKNOWN;
1638
1639 char cur[PATH_MAX];
1640 snprintf(cur, sizeof(cur), "%s/cur", path);
1641
1642 struct stat st_cur = { 0 };
1643 if ((stat(cur, &st_cur) == 0) && S_ISDIR(st_cur.st_mode))
1644 return MUTT_MAILDIR;
1645
1646 return MUTT_UNKNOWN;
1647 }
1648
1649 /**
1650 * MxMaildirOps - Maildir Mailbox - Implements ::MxOps - @ingroup mx_api
1651 */
1652 struct MxOps MxMaildirOps = {
1653 // clang-format off
1654 .type = MUTT_MAILDIR,
1655 .name = "maildir",
1656 .is_local = true,
1657 .ac_owns_path = maildir_ac_owns_path,
1658 .ac_add = maildir_ac_add,
1659 .mbox_open = maildir_mbox_open,
1660 .mbox_open_append = maildir_mbox_open_append,
1661 .mbox_check = maildir_mbox_check,
1662 .mbox_check_stats = maildir_mbox_check_stats,
1663 .mbox_sync = maildir_mbox_sync,
1664 .mbox_close = maildir_mbox_close,
1665 .msg_open = maildir_msg_open,
1666 .msg_open_new = maildir_msg_open_new,
1667 .msg_commit = maildir_msg_commit,
1668 .msg_close = maildir_msg_close,
1669 .msg_padding_size = NULL,
1670 .msg_save_hcache = maildir_msg_save_hcache,
1671 .tags_edit = NULL,
1672 .tags_commit = NULL,
1673 .path_probe = maildir_path_probe,
1674 .path_canon = maildir_path_canon,
1675 .path_pretty = maildir_path_pretty,
1676 .path_parent = maildir_path_parent,
1677 .path_is_empty = maildir_check_empty,
1678 // clang-format on
1679 };
1680