summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-fs-flags.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-fs-flags.c')
-rw-r--r--src/lib-storage/list/mailbox-list-fs-flags.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-fs-flags.c b/src/lib-storage/list/mailbox-list-fs-flags.c
new file mode 100644
index 0000000..3f3bd94
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs-flags.c
@@ -0,0 +1,243 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mailbox-list-fs.h"
+
+#include <sys/stat.h>
+
+/* Assume that if atime < mtime, there are new mails. If it's good enough for
+ UW-IMAP, it's good enough for us. */
+#define STAT_GET_MARKED_FILE(st) \
+ ((st).st_size == 0 ? MAILBOX_UNMARKED : \
+ (st).st_atime < (st).st_mtime ? MAILBOX_MARKED : MAILBOX_UNMARKED)
+
+static int
+list_is_maildir_mailbox(struct mailbox_list *list, const char *dir,
+ const char *fname, enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags_r)
+{
+ const char *path, *maildir_path;
+ struct stat st, st2;
+ bool mailbox_files;
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ case MAILBOX_LIST_FILE_TYPE_OTHER:
+ /* non-directories aren't valid */
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
+ case MAILBOX_LIST_FILE_TYPE_SYMLINK:
+ break;
+ }
+
+ path = t_strdup_printf("%s/%s", dir, fname);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ } else {
+ /* non-selectable. probably either access denied, or
+ symlink destination not found. don't bother logging
+ errors. */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ if (str_begins(fname, ".nfs")) {
+ /* temporary NFS file */
+ *flags_r |= MAILBOX_NONEXISTENT;
+ } else {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ }
+ return 0;
+ }
+
+ /* ok, we've got a directory. see what we can do about it. */
+
+ /* 1st link is "."
+ 2nd link is ".."
+ 3rd link is either child mailbox or mailbox dir
+ rest of the links are child mailboxes
+
+ if mailboxes are files, then 3+ links are all child mailboxes.
+ */
+ mailbox_files = (list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0;
+ if (st.st_nlink == 2 && !mailbox_files) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+
+ /* we have at least one directory. see if this mailbox is selectable */
+ maildir_path = t_strconcat(path, "/", list->set.maildir_name, NULL);
+ if (stat(maildir_path, &st2) < 0)
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_CHILDREN;
+ else if (!S_ISDIR(st2.st_mode)) {
+ if (mailbox_files) {
+ *flags_r |= st.st_nlink == 2 ?
+ MAILBOX_NOCHILDREN : MAILBOX_CHILDREN;
+ } else {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_CHILDREN;
+ }
+ } else {
+ /* now we know what link count 3 means. */
+ if (st.st_nlink == 3)
+ *flags_r |= MAILBOX_NOCHILDREN;
+ else
+ *flags_r |= MAILBOX_CHILDREN;
+ }
+ *flags_r |= MAILBOX_SELECT;
+ return 1;
+}
+
+static bool
+is_inbox_file(struct mailbox_list *list, const char *path, const char *fname)
+{
+ const char *inbox_path;
+
+ if (strcasecmp(fname, "INBOX") != 0)
+ return FALSE;
+
+ if (mailbox_list_get_path(list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &inbox_path) <= 0)
+ i_unreached();
+ return strcmp(inbox_path, path) == 0;
+}
+
+int fs_list_get_mailbox_flags(struct mailbox_list *list,
+ const char *dir, const char *fname,
+ enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags_r)
+{
+ struct stat st;
+ const char *path;
+
+ *flags_r = 0;
+
+ if (*list->set.maildir_name != '\0' && !list->set.iter_from_index_dir) {
+ /* maildir_name is set: This is the simple case that works for
+ all mail storage formats, because the only thing that
+ matters for existence or child checks is whether the
+ maildir_name exists or not. For example with Maildir this
+ doesn't care whether the "cur" directory exists; as long
+ as the parent maildir_name exists, the Maildir is
+ selectable. */
+ return list_is_maildir_mailbox(list, dir, fname, type, flags_r);
+ }
+ /* maildir_name is not set: Now we (may) need to use storage-specific
+ code to determine whether the mailbox is selectable or if it has
+ children.
+
+ We're here also when iterating from index directory, because even
+ though maildir_name is set, it's not used for index directory.
+ */
+
+ if (!list->set.iter_from_index_dir &&
+ list->v.is_internal_name != NULL &&
+ list->v.is_internal_name(list, fname)) {
+ /* skip internal dirs. For example Maildir's cur/new/tmp */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 0;
+ }
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ /* We know that we're looking at a directory. If the storage
+ uses files, it has to be a \NoSelect directory. */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ /* We know that we're looking at a file. If the storage
+ doesn't use files, it's not a mailbox and we want to skip
+ it. */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* we've done all filtering we can before stat()ing */
+ path = t_strconcat(dir, "/", fname, NULL);
+ if (stat(path, &st) < 0) {
+ if (ENOTFOUND(errno)) {
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ } else if (ENOACCESS(errno)) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ } else {
+ /* non-selectable. probably either access denied, or
+ symlink destination not found. don't bother logging
+ errors. */
+ mailbox_list_set_critical(list, "stat(%s) failed: %m",
+ path);
+ return -1;
+ }
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ if (str_begins(fname, ".nfs")) {
+ /* temporary NFS file */
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ }
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+ }
+ /* looks like a valid mailbox file */
+ if (is_inbox_file(list, path, fname) &&
+ strcmp(fname, "INBOX") != 0) {
+ /* it's possible for INBOX to have child
+ mailboxes as long as the inbox file itself
+ isn't in <mail root>/INBOX */
+ } else {
+ *flags_r |= MAILBOX_NOINFERIORS;
+ }
+ /* Return mailbox files as always existing. The current
+ mailbox_exists() code would do the same stat() anyway
+ without further checks, so might as well avoid the second
+ stat(). */
+ *flags_r |= MAILBOX_SELECT;
+ *flags_r |= STAT_GET_MARKED_FILE(st);
+ return 1;
+ }
+
+ /* This is a directory */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ /* We should get here only if type is
+ MAILBOX_LIST_FILE_TYPE_UNKNOWN because the filesystem didn't
+ return the type. Normally this should have already been
+ handled by the MAILBOX_LIST_FILE_TYPE_DIR check above. */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+
+ if (list->v.is_internal_name == NULL || list->set.iter_from_index_dir) {
+ /* This mailbox format doesn't use any special directories
+ (e.g. Maildir's cur/new/tmp). In that case we can look at
+ the directory's link count to determine whether there are
+ children or not. The directory's link count equals the
+ number of subdirectories it has. The first two links are
+ for "." and "..".
+
+ link count < 2 can happen with filesystems that don't
+ support link counts. we'll just ignore them for now.. */
+ if (st.st_nlink == 2)
+ *flags_r |= MAILBOX_NOCHILDREN;
+ else if (st.st_nlink > 2)
+ *flags_r |= MAILBOX_CHILDREN;
+ }
+ return 1;
+}