# seems to think they are FETCH replies and gives user "Message no longer
# in server" error. Note that OE6 still breaks even with this workaround
# if synchronization is set to "Headers Only".
+# list-sort:
+# When replying to LIST and LSUB requests, make sure that the parent
+# mailboxes are sent before any of their children. This is mostly
+# maildir-specific, mbox list replies are always sorted. MacOS X's Mail.app
+# at least wants this.
#client_workarounds =
# Dovecot can notify client of new mail in selected mailbox soon after it's
return *node;
}
-static void list_cb(struct mail_storage *storage __attr_unused__,
- const char *name, enum mailbox_flags flags, void *context)
-{
- struct list_context *ctx = context;
- struct list_node *node;
-
- node = list_node_get(ctx->pool, &ctx->nodes, name,
- ctx->storage->hierarchy_sep);
-
- /* set the flags, this also nicely overrides the NOSELECT flag
- set by list_node_get() */
- node->flags = flags;
-}
-
static void list_send(struct client *client, struct list_node *node,
const char *cmd, const char *path, const char *sep)
{
}
}
+static void list_unsorted(struct client *client,
+ struct mailbox_list_context *ctx,
+ const char *cmd, const char *sep)
+{
+ struct mailbox_list *list;
+ const char *name, *str;
+
+ while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
+ t_push();
+ if (strcasecmp(list->name, "INBOX") == 0)
+ name = "INBOX";
+ else
+ name = str_escape(list->name);
+ str = t_strdup_printf("* %s (%s) \"%s\" \"%s\"", cmd,
+ mailbox_flags2str(list->flags),
+ sep, name);
+ client_send_line(client, str);
+ t_pop();
+ }
+}
+
+static void list_and_sort(struct client *client,
+ struct mailbox_list_context *ctx,
+ const char *cmd, const char *sep)
+{
+ struct mailbox_list *list;
+ struct list_node *nodes, *node;
+ pool_t pool;
+
+ pool = pool_alloconly_create("list_mailboxes", 10240);
+ nodes = NULL;
+
+ while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
+ node = list_node_get(pool, &nodes, list->name,
+ client->storage->hierarchy_sep);
+
+ /* set the flags, this also overrides the
+ NOSELECT flag set by list_node_get() */
+ node->flags = list->flags;
+ }
+
+ list_send(client, nodes, cmd, NULL, sep);
+ pool_unref(pool);
+}
+
+static int list_mailboxes(struct client *client, const char *mask,
+ int subscribed, const char *sep)
+{
+ struct mailbox_list_context *ctx;
+ const char *cmd;
+ int sorted;
+
+ ctx = client->storage->
+ list_mailbox_init(client->storage, mask,
+ subscribed ? MAILBOX_LIST_SUBSCRIBED : 0,
+ &sorted);
+ if (ctx == NULL)
+ return FALSE;
+
+ cmd = subscribed ? "LSUB" : "LIST";
+ if (sorted || (client_workarounds & WORKAROUND_LIST_SORT) == 0)
+ list_unsorted(client, ctx, cmd, sep);
+ else
+ list_and_sort(client, ctx, cmd, sep);
+
+ return client->storage->list_mailbox_deinit(ctx);
+}
+
int _cmd_list_full(struct client *client, int subscribed)
{
- struct list_context ctx;
- const char *ref, *pattern;
+ const char *ref, *mask;
char sep_chr, sep[3];
int failed;
}
/* <reference> <mailbox wildcards> */
- if (!client_read_string_args(client, 2, &ref, &pattern))
+ if (!client_read_string_args(client, 2, &ref, &mask))
return FALSE;
- if (*pattern == '\0' && !subscribed) {
+ if (*mask == '\0' && !subscribed) {
/* special request to return the hierarchy delimiter */
client_send_line(client, t_strconcat(
"* LIST (\\Noselect) \"", sep, "\" \"\"", NULL));
failed = FALSE;
} else {
if (*ref != '\0') {
- /* join reference + pattern */
- if (*pattern == sep_chr &&
+ /* join reference + mask */
+ if (*mask == sep_chr &&
ref[strlen(ref)-1] == sep_chr) {
/* LIST A. .B -> A.B */
- pattern++;
+ mask++;
}
- if (*pattern != sep_chr &&
+ if (*mask != sep_chr &&
ref[strlen(ref)-1] != sep_chr) {
/* LIST A B -> A.B */
- pattern = t_strconcat(ref, sep, pattern, NULL);
+ mask = t_strconcat(ref, sep, mask, NULL);
} else {
- pattern = t_strconcat(ref, pattern, NULL);
+ mask = t_strconcat(ref, mask, NULL);
}
}
- ctx.pool = pool_alloconly_create("list_context", 10240);
- ctx.nodes = NULL;
- ctx.storage = client->storage;
-
- if (!subscribed) {
- failed = !client->storage->
- find_mailboxes(client->storage,
- pattern, list_cb, &ctx);
- } else {
- failed = !client->storage->
- find_subscribed(client->storage,
- pattern, list_cb, &ctx);
- }
-
- if (!failed) {
- list_send(client, ctx.nodes,
- subscribed ? "LSUB" : "LIST", NULL, sep);
- }
- pool_unref(ctx.pool);
+ failed = !list_mailboxes(client, mask, subscribed, sep);
}
if (failed)
#include <ctype.h>
struct imap_match_glob {
+ pool_t pool;
+
int inboxcase;
const char *inboxcase_end;
static const char inbox[] = "INBOX";
#define INBOXLEN (sizeof(inbox) - 1)
-struct imap_match_glob *imap_match_init(const char *mask, int inboxcase,
- char separator)
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *mask, int inboxcase, char separator)
{
struct imap_match_glob *glob;
const char *p, *inboxp;
char *dst;
/* +1 from struct */
- glob = t_malloc(sizeof(struct imap_match_glob) + strlen(mask));
+ glob = p_malloc(pool, sizeof(struct imap_match_glob) + strlen(mask));
+ glob->pool = pool;
glob->sep_char = separator;
/* @UNSAFE: compress the mask */
}
if (glob->inboxcase && inboxp != NULL && *inboxp != '\0' &&
- *p != '*' && (p != glob->mask && p[-1] == '%'))
+ *p != '*' && (p != glob->mask && p[-1] != '%'))
glob->inboxcase = FALSE;
}
return glob;
}
+void imap_match_deinit(struct imap_match_glob *glob)
+{
+ p_free(glob->pool, glob);
+}
+
static inline int cmp_chr(const struct imap_match_glob *glob,
const char *data, char maskchr)
{
i_toupper(*data) == i_toupper(maskchr));
}
-static int match_sub(const struct imap_match_glob *glob, const char **data_p,
- const char **mask_p)
+static enum imap_match_result
+match_sub(const struct imap_match_glob *glob, const char **data_p,
+ const char **mask_p)
{
const char *mask, *data;
- int ret, best_ret;
+ enum imap_match_result ret, best_ret;
data = *data_p; mask = *mask_p;
while (*mask != '\0' && *mask != '*' && *mask != '%') {
if (!cmp_chr(glob, data, *mask)) {
return *data == '\0' && *mask == glob->sep_char ?
- 0 : -1;
+ IMAP_MATCH_CHILDREN : IMAP_MATCH_NO;
}
data++; mask++;
}
- best_ret = -1;
+ best_ret = IMAP_MATCH_NO;
while (*mask == '%') {
mask++;
if (ret > 0)
break;
- if (ret == 0)
- best_ret = 0;
+ if (ret == IMAP_MATCH_CHILDREN)
+ best_ret = IMAP_MATCH_CHILDREN;
}
if (*data == glob->sep_char)
if (*mask != '*') {
if (*data == '\0' && *mask != '\0')
- return *mask == glob->sep_char ? 0 : best_ret;
+ return *mask == glob->sep_char ?
+ IMAP_MATCH_CHILDREN : best_ret;
- if (*data != '\0')
- return best_ret;
+ if (*data != '\0') {
+ return best_ret != IMAP_MATCH_NO ||
+ *mask != '\0' || *data != glob->sep_char ?
+ best_ret : IMAP_MATCH_PARENT;
+ }
}
*data_p = data;
*mask_p = mask;
- return 1;
+ return IMAP_MATCH_YES;
}
-int imap_match(struct imap_match_glob *glob, const char *data)
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data)
{
const char *mask;
int ret;
return ret;
if (*mask == '\0')
- return 1;
+ return IMAP_MATCH_YES;
}
while (*mask == '*') {
mask++;
if (*mask == '\0')
- return 1;
+ return IMAP_MATCH_YES;
while (*data != '\0') {
if (cmp_chr(glob, data, *mask)) {
}
}
- return *data == '\0' && *mask == '\0' ? 1 : 0;
+ return *data == '\0' && *mask == '\0' ?
+ IMAP_MATCH_YES : IMAP_MATCH_CHILDREN;
}
#ifndef __IMAP_MATCH_H
#define __IMAP_MATCH_H
+enum imap_match_result {
+ IMAP_MATCH_YES = 1, /* match */
+ IMAP_MATCH_NO = -1, /* definite non-match */
+
+ IMAP_MATCH_CHILDREN = 0, /* it's children might match */
+ IMAP_MATCH_PARENT = -2 /* one of it's parents would match */
+};
+
struct imap_match_glob;
/* If inboxcase is TRUE, the "INBOX" string at the beginning of line is
compared case-insensitively */
-struct imap_match_glob *imap_match_init(const char *mask, int inboxcase,
- char separator);
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *mask, int inboxcase, char separator);
+
+void imap_match_deinit(struct imap_match_glob *glob);
-/* Returns 1 if matched, 0 if it didn't match, but could match with additional
- hierarchies, -1 if definitely didn't match */
-int imap_match(struct imap_match_glob *glob, const char *data);
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data);
#endif
#include <dirent.h>
#include <sys/stat.h>
-struct find_subscribed_context {
- mailbox_list_callback_t *callback;
- void *context;
+struct mailbox_list_context {
+ pool_t pool, list_pool;
+
+ struct mail_storage *storage;
+ const char *dir, *prefix;
+ enum mailbox_list_flags flags;
+
+ DIR *dirp;
+ struct imap_match_glob *glob;
+ struct subsfile_list_context *subsfile_ctx;
+
+ struct mailbox_list *(*next)(struct mailbox_list_context *ctx);
+
+ struct mailbox_list list;
+ int found_inbox, failed;
};
+static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx);
+static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx);
+
static enum mailbox_flags
maildir_get_marked_flags_from(const char *dir, time_t index_stamp)
{
return maildir_get_marked_flags_from(dir, st.st_mtime);
}
-int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context)
+struct mailbox_list_context *
+maildir_list_mailbox_init(struct mail_storage *storage,
+ const char *mask, enum mailbox_list_flags flags,
+ int *sorted)
{
- struct imap_match_glob *glob;
- DIR *dirp;
- struct dirent *d;
- struct stat st;
- enum mailbox_flags flags;
- const char *dir, *prefix, *p;
- char path[PATH_MAX];
- int failed, found_inbox, ret;
+ struct mailbox_list_context *ctx;
+ pool_t pool;
+ const char *dir, *p;
+ *sorted = FALSE;
mail_storage_clear_error(storage);
+ pool = pool_alloconly_create("maildir_list", 1024);
+ ctx = p_new(pool, struct mailbox_list_context, 1);
+ ctx->pool = pool;
+ ctx->storage = storage;
+ ctx->flags = flags;
+
+ if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
+ ctx->glob = imap_match_init(pool, mask, TRUE, '.');
+ ctx->subsfile_ctx = subsfile_list_init(storage);
+ ctx->next = maildir_list_subs;
+ if (ctx->subsfile_ctx == NULL) {
+ pool_unref(pool);
+ return NULL;
+ }
+ return ctx;
+ }
+
if (!full_filesystem_access || (p = strrchr(mask, '/')) == NULL) {
- dir = storage->dir;
- prefix = "";
+ ctx->dir = storage->dir;
+ ctx->prefix = "";
} else {
p = strchr(p, storage->hierarchy_sep);
if (p == NULL) {
/* this isn't going to work */
mail_storage_set_error(storage, "Invalid list mask");
+ pool_unref(pool);
return FALSE;
}
dir = t_strdup_until(mask, p);
- prefix = t_strdup_until(mask, p+1);
+ ctx->prefix = t_strdup_until(mask, p+1);
if (*mask != '/' && *mask != '~')
dir = t_strconcat(storage->dir, "/", dir, NULL);
- dir = home_expand(dir);
+ ctx->dir = p_strdup(pool, home_expand(dir));
}
- dirp = opendir(dir);
- if (dirp == NULL) {
+ ctx->dirp = opendir(ctx->dir);
+ if (ctx->dirp == NULL) {
mail_storage_set_critical(storage, "opendir(%s) failed: %m",
- dir);
- return FALSE;
+ ctx->dir);
+ pool_unref(pool);
+ return NULL;
+ }
+
+ ctx->list_pool = pool_alloconly_create("maildir_list.list", 4096);
+ ctx->glob = imap_match_init(pool, mask, TRUE, '.');
+ ctx->next = maildir_list_next;
+ return ctx;
+}
+
+int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx)
+{
+ int failed;
+
+ if (ctx->subsfile_ctx != NULL)
+ failed = !subsfile_list_deinit(ctx->subsfile_ctx);
+ else
+ failed = ctx->failed;
+
+ if (ctx->dirp != NULL)
+ (void)closedir(ctx->dirp);
+ if (ctx->list_pool != NULL)
+ pool_unref(ctx->list_pool);
+ imap_match_deinit(ctx->glob);
+ pool_unref(ctx->pool);
+
+ return !failed;
+}
+
+static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx)
+{
+ struct stat st;
+ const char *name, *path, *p;
+ enum imap_match_result match = IMAP_MATCH_NO;
+
+ while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
+ match = imap_match(ctx->glob, name);
+ if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
+ break;
+ }
+
+ if (name == NULL)
+ return NULL;
+
+ ctx->list.flags = 0;
+ ctx->list.name = name;
+
+ if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) != 0)
+ return &ctx->list;
+
+ if (match == IMAP_MATCH_PARENT) {
+ /* placeholder */
+ ctx->list.flags = MAILBOX_NOSELECT;
+ while ((p = strrchr(name, '.')) != NULL) {
+ name = t_strdup_until(name, p);
+ if (imap_match(ctx->glob, name) > 0) {
+ ctx->list.name = name;
+ return &ctx->list;
+ }
+ }
+ i_unreached();
+ }
+
+ t_push();
+ path = maildir_get_path(ctx->storage, ctx->list.name);
+ if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+ ctx->list.flags = maildir_get_marked_flags(ctx->storage, path);
+ else {
+ if (strcasecmp(ctx->list.name, "INBOX") == 0)
+ ctx->list.flags = 0;
+ else
+ ctx->list.flags = MAILBOX_NOSELECT;
}
+ t_pop();
+ return &ctx->list;
+}
- glob = imap_match_init(mask, TRUE, '.');
+static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx)
+{
+ struct dirent *d;
+ struct stat st;
+ char path[PATH_MAX];
+ int ret;
- failed = found_inbox = FALSE;
- while ((d = readdir(dirp)) != NULL) {
+ if (ctx->dirp == NULL)
+ return NULL;
+
+ while ((d = readdir(ctx->dirp)) != NULL) {
const char *fname = d->d_name;
if (fname[0] != '.')
/* make sure the mask matches - dirs beginning with ".."
should be deleted and we always want to check those. */
t_push();
- ret = imap_match(glob, t_strconcat(prefix, fname+1, NULL));
+ ret = imap_match(ctx->glob,
+ t_strconcat(ctx->prefix, fname+1, NULL));
t_pop();
if (fname[1] == '.' || ret <= 0)
continue;
- if (str_path(path, sizeof(path), dir, fname) < 0)
+ if (str_path(path, sizeof(path), ctx->dir, fname) < 0)
continue;
/* make sure it's a directory */
- if (stat(path, &st) != 0) {
+ if (stat(path, &st) < 0) {
if (errno == ENOENT)
continue; /* just deleted, ignore */
- mail_storage_set_critical(storage,
+ mail_storage_set_critical(ctx->storage,
"stat(%s) failed: %m", path);
- failed = TRUE;
- break;
+ ctx->failed = TRUE;
+ return NULL;
}
if (!S_ISDIR(st.st_mode))
}
if (strcasecmp(fname+1, "INBOX") == 0) {
- if (found_inbox) {
+ if (ctx->found_inbox) {
/* another inbox, ignore it */
continue;
}
- found_inbox = TRUE;
+ ctx->found_inbox = TRUE;
}
- t_push();
- flags = maildir_get_marked_flags(storage, path);
- callback(storage, t_strconcat(prefix, fname+1, NULL),
- flags, context);
- t_pop();
+ p_clear(ctx->list_pool);
+ if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) == 0) {
+ ctx->list.flags =
+ maildir_get_marked_flags(ctx->storage, path);
+ }
+ ctx->list.name = p_strconcat(ctx->list_pool,
+ ctx->prefix, fname+1, NULL);
+ return &ctx->list;
}
- if (!failed && !found_inbox && imap_match(glob, "INBOX") > 0) {
- /* .INBOX directory doesn't exist yet, but INBOX still exists */
- callback(storage, "INBOX", 0, context);
+ if (closedir(ctx->dirp) < 0) {
+ mail_storage_set_critical(ctx->storage,
+ "closedir(%s) failed: %m", ctx->dir);
+ ctx->failed = TRUE;
}
+ ctx->dirp = NULL;
- (void)closedir(dirp);
- return !failed;
-}
-
-static int maildir_subs_cb(struct mail_storage *storage, const char *name,
- void *context)
-{
- struct find_subscribed_context *ctx = context;
- enum mailbox_flags flags;
- struct stat st;
- char path[PATH_MAX];
-
- if (str_ppath(path, sizeof(path), storage->dir, ".", name) < 0)
- flags = MAILBOX_NOSELECT;
- else {
- if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
- flags = maildir_get_marked_flags(storage, path);
- else
- flags = MAILBOX_NOSELECT;
+ if (!ctx->found_inbox && imap_match(ctx->glob, "INBOX") > 0) {
+ /* .INBOX directory doesn't exist yet, but INBOX still exists */
+ ctx->list.flags = 0;
+ ctx->list.name = "INBOX";
+ return &ctx->list;
}
- ctx->callback(storage, name, flags, ctx->context);
- return TRUE;
+ /* we're finished */
+ return NULL;
}
-int maildir_find_subscribed(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context)
+struct mailbox_list *
+maildir_list_mailbox_next(struct mailbox_list_context *ctx)
{
- struct find_subscribed_context ctx;
-
- ctx.callback = callback;
- ctx.context = context;
-
- if (subsfile_foreach(storage, mask, maildir_subs_cb, &ctx) <= 0)
- return FALSE;
-
- return TRUE;
+ return ctx->next(ctx);
}
return t_strconcat(t_strdup_until(name, p), "/", p, NULL);
}
-static const char *maildir_get_path(struct mail_storage *storage,
- const char *name)
+const char *maildir_get_path(struct mail_storage *storage, const char *name)
{
if (full_filesystem_access && (*name == '/' || *name == '~'))
return maildir_get_absolute_path(name);
return TRUE;
}
-static void rename_subfolder(struct mail_storage *storage, const char *name,
- enum mailbox_flags flags __attr_unused__,
- void *context)
+static int rename_subfolders(struct mail_storage *storage,
+ const char *oldname, const char *newname)
{
- struct rename_context *ctx = context;
- const char *newname, *oldpath, *newpath;
-
- i_assert(ctx->oldnamelen <= strlen(name));
-
- newname = t_strconcat(ctx->newname, ".", name + ctx->oldnamelen, NULL);
-
- oldpath = maildir_get_path(storage, name);
- newpath = maildir_get_path(storage, newname);
-
- /* FIXME: it's possible to merge two folders if either one of them
- doesn't have existing root folder. We could check this but I'm not
- sure if it's worth it. It could be even considered as a feature.
+ struct mailbox_list_context *ctx;
+ struct mailbox_list *list;
+ const char *oldpath, *newpath, *new_listname;
+ size_t oldnamelen;
+ int sorted, ret;
+
+ ret = 0;
+ oldnamelen = strlen(oldname);
+
+ ctx = storage->list_mailbox_init(storage,
+ t_strconcat(oldname, ".*", NULL),
+ MAILBOX_LIST_NO_FLAGS, &sorted);
+ while ((list = maildir_list_mailbox_next(ctx)) != NULL) {
+ i_assert(oldnamelen <= strlen(list->name));
+
+ t_push();
+ new_listname = t_strconcat(newname, ".",
+ list->name + oldnamelen, NULL);
+ oldpath = maildir_get_path(storage, list->name);
+ newpath = maildir_get_path(storage, new_listname);
+
+ /* FIXME: it's possible to merge two folders if either one of
+ them doesn't have existing root folder. We could check this
+ but I'm not sure if it's worth it. It could be even
+ considered as a feature.
+
+ Anyway, the bug with merging is that if both folders have
+ identically named subfolder they conflict. Just ignore those
+ and leave them under the old folder. */
+ if (rename(oldpath, newpath) == 0 ||
+ errno == EEXIST || errno == ENOTEMPTY)
+ ret = 1;
+ else {
+ mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ ret = -1;
+ t_pop();
+ break;
+ }
- Anyway, the bug with merging is that if both folders have
- identically named subfolder they conflict. Just ignore those and
- leave them under the old folder. */
- if (rename(oldpath, newpath) == 0 || errno == EEXIST)
- ctx->found = TRUE;
- else {
- mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
- oldpath, newpath);
+ (void)rename_indexes(storage, list->name, new_listname);
+ t_pop();
}
- (void)rename_indexes(storage, name, newname);
+ if (!maildir_list_mailbox_deinit(ctx))
+ return -1;
+ return ret;
}
static int maildir_rename_mailbox(struct mail_storage *storage,
const char *oldname, const char *newname)
{
- struct rename_context ctx;
const char *oldpath, *newpath;
- int ret;
+ int ret, found;
mail_storage_clear_error(storage);
if (!rename_indexes(storage, oldname, newname))
return FALSE;
- ctx.found = ret == 0;
- ctx.oldnamelen = strlen(oldname)+1;
- ctx.newname = newname;
- if (!maildir_find_mailboxes(storage,
- t_strconcat(oldname, ".*", NULL),
- rename_subfolder, &ctx))
+ found = ret == 0;
+ ret = rename_subfolders(storage, oldname, newname);
+ if (ret < 0)
return FALSE;
-
- if (!ctx.found) {
+ if (!found && ret == 0) {
mail_storage_set_error(storage,
"Mailbox doesn't exist");
return FALSE;
}
+
return TRUE;
}
maildir_create_mailbox,
maildir_delete_mailbox,
maildir_rename_mailbox,
- maildir_find_mailboxes,
+ maildir_list_mailbox_init,
+ maildir_list_mailbox_deinit,
+ maildir_list_mailbox_next,
subsfile_set_subscribed,
- maildir_find_subscribed,
maildir_get_mailbox_name_status,
mail_storage_get_last_error,
time_t received_date, int timezone_offset,
struct istream *data);
-int maildir_find_mailboxes(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context);
-int maildir_find_subscribed(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context);
+struct mailbox_list_context *
+maildir_list_mailbox_init(struct mail_storage *storage,
+ const char *mask, enum mailbox_list_flags flags,
+ int *sorted);
+int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx);
+struct mailbox_list *
+maildir_list_mailbox_next(struct mailbox_list_context *ctx);
int maildir_expunge_locked(struct index_mailbox *ibox, int notify);
/* Return new filename base to save into tmp/ */
const char *maildir_generate_tmp_filename(void);
+const char *maildir_get_path(struct mail_storage *storage, const char *name);
+
#endif
#include <dirent.h>
#include <sys/stat.h>
-struct find_subscribed_context {
- mailbox_list_callback_t *callback;
- void *context;
+#define STAT_GET_MARKED(st) \
+ ((st).st_size != 0 && (st).st_atime < (st).st_ctime ? \
+ MAILBOX_MARKED : MAILBOX_UNMARKED)
+
+struct list_dir_context {
+ struct list_dir_context *prev;
+
+ DIR *dirp;
+ char *real_path, *virtual_path;
};
-struct list_context {
+struct mailbox_list_context {
struct mail_storage *storage;
+ enum mailbox_list_flags flags;
+
struct imap_match_glob *glob;
- mailbox_list_callback_t *callback;
- void *context;
+ struct subsfile_list_context *subsfile_ctx;
- const char *rootdir;
+ int failed;
+
+ struct mailbox_list *(*next)(struct mailbox_list_context *ctx);
+
+ pool_t list_pool;
+ struct mailbox_list list;
+ struct list_dir_context *dir;
};
-static int mbox_find_path(struct list_context *ctx, const char *relative_dir)
+static struct mailbox_list *mbox_list_subs(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_inbox(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_path(struct mailbox_list_context *ctx);
+static struct mailbox_list *mbox_list_next(struct mailbox_list_context *ctx);
+
+static const char *mask_get_dir(const char *mask)
{
- DIR *dirp;
- struct dirent *d;
- struct stat st;
- const char *dir, *listpath;
- char fulldir[PATH_MAX], path[PATH_MAX], fullpath[PATH_MAX];
- int failed, match;
- size_t len;
+ const char *p, *last_dir;
- t_push();
+ last_dir = NULL;
+ for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
+ if (*p == '/')
+ last_dir = p;
+ }
- if (relative_dir == NULL)
- dir = ctx->rootdir;
- else if (*ctx->rootdir == '\0' && *relative_dir != '\0')
- dir = relative_dir;
- else {
- if (str_path(fulldir, sizeof(fulldir),
- ctx->rootdir, relative_dir) < 0) {
- mail_storage_set_critical(ctx->storage,
- "Path too long: %s",
- relative_dir);
- return FALSE;
- }
+ return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
+}
- dir = fulldir;
- }
+static const char *mbox_get_path(struct mail_storage *storage, const char *name)
+{
+ if (!full_filesystem_access || name == NULL ||
+ (*name != '/' && *name != '~' && *name != '\0'))
+ return t_strconcat(storage->dir, "/", name, NULL);
+ else
+ return home_expand(name);
+}
- dir = home_expand(dir);
- dirp = opendir(dir);
- if (dirp == NULL) {
- t_pop();
+static int list_opendir(struct mail_storage *storage,
+ const char *path, int root, DIR **dirp)
+{
+ *dirp = opendir(*path == '\0' ? "/" : path);
+ if (*dirp != NULL)
+ return 1;
+
+ if (!root && (errno == ENOENT || errno == ENOTDIR)) {
+ /* probably just race condition with other client
+ deleting the mailbox. */
+ return 0;
+ }
- if (relative_dir != NULL &&
- (errno == ENOENT || errno == ENOTDIR)) {
- /* probably just race condition with other client
- deleting the mailbox. */
- return TRUE;
+ if (errno == EACCES) {
+ if (!root) {
+ /* subfolder, ignore */
+ return 0;
}
+ mail_storage_set_error(storage, "Access denied");
+ return -1;
+ }
- if (errno == EACCES) {
- if (relative_dir != NULL) {
- /* subfolder, ignore */
- return TRUE;
- }
- mail_storage_set_error(ctx->storage, "Access denied");
- return FALSE;
- }
+ mail_storage_set_critical(storage, "opendir(%s) failed: %m", path);
+ return -1;
+}
- mail_storage_set_critical(ctx->storage,
- "opendir(%s) failed: %m", dir);
- return FALSE;
- }
+struct mailbox_list_context *
+mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
+ enum mailbox_list_flags flags, int *sorted)
+{
+ struct mailbox_list_context *ctx;
+ const char *path, *virtual_path;
+ DIR *dirp;
- failed = FALSE;
- while ((d = readdir(dirp)) != NULL) {
- const char *fname = d->d_name;
+ *sorted = (flags & MAILBOX_LIST_SUBSCRIBED) == 0;
- /* skip all hidden files */
- if (fname[0] == '.')
- continue;
+ /* check that we're not trying to do any "../../" lists */
+ if (!mbox_is_valid_mask(mask)) {
+ mail_storage_set_error(storage, "Invalid mask");
+ return NULL;
+ }
- /* skip all .lock files */
- len = strlen(fname);
- if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
- continue;
+ mail_storage_clear_error(storage);
- /* check the mask */
- if (relative_dir == NULL)
- listpath = fname;
- else {
- if (str_path(path, sizeof(path),
- relative_dir, fname) < 0) {
- mail_storage_set_critical(ctx->storage,
- "Path too long: %s/%s",
- relative_dir, fname);
- failed = TRUE;
- break;
- }
- listpath = path;
+ if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
+ ctx = i_new(struct mailbox_list_context, 1);
+ ctx->storage = storage;
+ ctx->flags = flags;
+ ctx->next = mbox_list_subs;
+ ctx->subsfile_ctx = subsfile_list_init(storage);
+ if (ctx->subsfile_ctx == NULL) {
+ i_free(ctx);
+ return NULL;
}
+ ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
+ return ctx;
+ }
- if ((match = imap_match(ctx->glob, listpath)) < 0)
- continue;
+ /* if we're matching only subdirectories, don't bother scanning the
+ parent directories */
+ virtual_path = mask_get_dir(mask);
+
+ path = mbox_get_path(storage, virtual_path);
+ if (list_opendir(storage, path, TRUE, &dirp) <= 0)
+ return NULL;
+
+ ctx = i_new(struct mailbox_list_context, 1);
+ ctx->storage = storage;
+ ctx->flags = flags;
+ ctx->glob = imap_match_init(default_pool, mask, TRUE, '/');
+ ctx->list_pool = pool_alloconly_create("mbox_list", 1024);
+
+ if (virtual_path == NULL && imap_match(ctx->glob, "INBOX") > 0)
+ ctx->next = mbox_list_inbox;
+ else if (virtual_path != NULL)
+ ctx->next = mbox_list_path;
+ else
+ ctx->next = mbox_list_next;
- /* see if it's a directory */
- if (str_path(fullpath, sizeof(fullpath), dir, fname) < 0) {
- mail_storage_set_critical(ctx->storage,
- "Path too long: %s/%s",
- dir, fname);
- failed = TRUE;
- break;
- }
+ ctx->dir = i_new(struct list_dir_context, 1);
+ ctx->dir->dirp = dirp;
+ ctx->dir->real_path = i_strdup(path);
+ ctx->dir->virtual_path = i_strdup(virtual_path);
+ return ctx;
+}
- if (stat(fullpath, &st) < 0) {
- if (errno == ENOENT)
- continue; /* just deleted, ignore */
+static void list_dir_context_free(struct list_dir_context *dir)
+{
+ (void)closedir(dir->dirp);
+ i_free(dir->real_path);
+ i_free(dir->virtual_path);
+ i_free(dir);
+}
+
+int mbox_list_mailbox_deinit(struct mailbox_list_context *ctx)
+{
+ int failed = ctx->failed;
- mail_storage_set_critical(ctx->storage,
- "stat(%s) failed: %m",
- fullpath);
+ if (ctx->subsfile_ctx != NULL) {
+ if (!subsfile_list_deinit(ctx->subsfile_ctx))
failed = TRUE;
- break;
- }
+ }
- if (S_ISDIR(st.st_mode)) {
- /* subdirectory, scan it too */
- t_push();
- ctx->callback(ctx->storage, listpath, MAILBOX_NOSELECT,
- ctx->context);
- t_pop();
+ while (ctx->dir != NULL) {
+ struct list_dir_context *dir = ctx->dir;
- if (!mbox_find_path(ctx, listpath)) {
- failed = TRUE;
- break;
- }
- } else if (match > 0 &&
- strcmp(fullpath, ctx->storage->inbox_file) != 0 &&
- strcasecmp(listpath, "INBOX") != 0) {
- /* don't match any INBOX here, it's added later.
- we might also have ~/mail/inbox, ~/mail/Inbox etc.
- Just ignore them for now. */
- t_push();
- ctx->callback(ctx->storage, listpath,
- MAILBOX_NOINFERIORS, ctx->context);
- t_pop();
- }
+ ctx->dir = dir->prev;
+ list_dir_context_free(dir);
}
- t_pop();
+ if (ctx->list_pool != NULL)
+ pool_unref(ctx->list_pool);
+ imap_match_deinit(ctx->glob);
+ i_free(ctx);
- (void)closedir(dirp);
return !failed;
}
-static const char *mask_get_dir(const char *mask)
+struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx)
{
- const char *p, *last_dir;
+ return ctx->next(ctx);
+}
- last_dir = NULL;
- for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
- if (*p == '/')
- last_dir = p;
+static int list_file(struct mailbox_list_context *ctx, const char *fname)
+{
+ struct list_dir_context *dir;
+ const char *list_path, *real_path, *path;
+ struct stat st;
+ DIR *dirp;
+ size_t len;
+ enum imap_match_result match, match2;
+ int ret;
+
+ /* skip all hidden files */
+ if (fname[0] == '.')
+ return 0;
+
+ /* skip all .lock files */
+ len = strlen(fname);
+ if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
+ return 0;
+
+ /* check the mask */
+ if (ctx->dir->virtual_path == NULL)
+ list_path = fname;
+ else {
+ list_path = t_strconcat(ctx->dir->virtual_path,
+ "/", fname, NULL);
}
- return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir);
-}
+ if ((match = imap_match(ctx->glob, list_path)) < 0)
+ return 0;
+
+ /* see if it's a directory */
+ real_path = t_strconcat(ctx->dir->real_path, "/", fname, NULL);
+ if (stat(real_path, &st) < 0) {
+ if (errno == ENOENT)
+ return 0; /* just deleted, ignore */
+ mail_storage_set_critical(ctx->storage, "stat(%s) failed: %m",
+ real_path);
+ return -1;
+ }
-int mbox_find_mailboxes(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context)
-{
- struct list_context ctx;
- struct imap_match_glob *glob;
- const char *relative_dir;
+ if (S_ISDIR(st.st_mode)) {
+ /* subdirectory. scan inside it. */
+ path = t_strconcat(list_path, "/", NULL);
+ match2 = imap_match(ctx->glob, path);
+
+ if (match > 0) {
+ ctx->list.flags = MAILBOX_NOSELECT;
+ ctx->list.name = p_strdup(ctx->list_pool, list_path);
+ } else if (match2 > 0) {
+ ctx->list.flags = MAILBOX_NOSELECT;
+ ctx->list.name = p_strdup(ctx->list_pool, path);
+ }
- /* check that we're not trying to do any "../../" lists */
- if (!mbox_is_valid_mask(mask)) {
- mail_storage_set_error(storage, "Invalid mask");
- return FALSE;
+ ret = match2 < 0 ? 0 :
+ list_opendir(ctx->storage, real_path, FALSE, &dirp);
+ if (ret > 0) {
+ dir = i_new(struct list_dir_context, 1);
+ dir->dirp = dirp;
+ dir->real_path = i_strdup(real_path);
+ dir->virtual_path = i_strdup(list_path);
+
+ dir->prev = ctx->dir;
+ ctx->dir = dir;
+ } else if (ret < 0)
+ return -1;
+ return match > 0 || match2 > 0;
+ } else if (match > 0 &&
+ strcmp(real_path, ctx->storage->inbox_file) != 0 &&
+ strcasecmp(list_path, "INBOX") != 0) {
+ /* don't match any INBOX here, it's added separately.
+ we might also have ~/mail/inbox, ~/mail/Inbox etc.
+ Just ignore them for now. */
+ ctx->list.flags = MAILBOX_NOINFERIORS | STAT_GET_MARKED(st);
+ ctx->list.name = p_strdup(ctx->list_pool, list_path);
+ return 1;
}
- mail_storage_clear_error(storage);
+ return 0;
+}
- /* if we're matching only subdirectories, don't bother scanning the
- parent directories */
- relative_dir = mask_get_dir(mask);
+static struct mailbox_list *mbox_list_subs(struct mailbox_list_context *ctx)
+{
+ struct stat st;
+ const char *name, *path, *p;
+ enum imap_match_result match = IMAP_MATCH_NO;
- glob = imap_match_init(mask, TRUE, '/');
- if (relative_dir == NULL && imap_match(glob, "INBOX") > 0) {
- /* INBOX exists always, even if the file doesn't. */
- callback(storage, "INBOX", MAILBOX_NOINFERIORS, context);
+ while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
+ match = imap_match(ctx->glob, name);
+ if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
+ break;
}
- memset(&ctx, 0, sizeof(ctx));
- ctx.storage = storage;
- ctx.glob = glob;
- ctx.callback = callback;
- ctx.context = context;
+ if (name == NULL)
+ return NULL;
- if (!full_filesystem_access || relative_dir == NULL ||
- (*relative_dir != '/' && *relative_dir != '~' &&
- *relative_dir != '\0'))
- ctx.rootdir = storage->dir;
- else
- ctx.rootdir = "";
+ ctx->list.flags = 0;
+ ctx->list.name = name;
- if (relative_dir != NULL) {
- const char *matchdir = t_strconcat(relative_dir, "/", NULL);
+ if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) != 0)
+ return &ctx->list;
- if (imap_match(ctx.glob, matchdir) > 0) {
- t_push();
- ctx.callback(ctx.storage, matchdir, MAILBOX_NOSELECT,
- ctx.context);
- t_pop();
+ if (match == IMAP_MATCH_PARENT) {
+ /* placeholder */
+ ctx->list.flags = MAILBOX_NOSELECT;
+ while ((p = strrchr(name, '/')) != NULL) {
+ name = t_strdup_until(name, p);
+ if (imap_match(ctx->glob, name) > 0) {
+ ctx->list.name = name;
+ return &ctx->list;
+ }
}
+ i_unreached();
}
- if (!mbox_find_path(&ctx, relative_dir))
- return FALSE;
-
- return TRUE;
+ t_push();
+ path = mbox_get_path(ctx->storage, ctx->list.name);
+ if (stat(path, &st) == 0) {
+ if (S_ISDIR(st.st_mode))
+ ctx->list.flags = MAILBOX_NOSELECT;
+ else {
+ ctx->list.flags = MAILBOX_NOINFERIORS |
+ STAT_GET_MARKED(st);
+ }
+ } else {
+ if (strcasecmp(ctx->list.name, "INBOX") == 0)
+ ctx->list.flags = MAILBOX_UNMARKED;
+ else
+ ctx->list.flags = MAILBOX_NOSELECT;
+ }
+ t_pop();
+ return &ctx->list;
}
-static int mbox_subs_cb(struct mail_storage *storage, const char *name,
- void *context)
+static struct mailbox_list *mbox_list_inbox(struct mailbox_list_context *ctx)
{
- struct find_subscribed_context *ctx = context;
- enum mailbox_flags flags;
struct stat st;
- char path[PATH_MAX];
- /* see if the mailbox exists, don't bother with the marked flags */
- if (strcasecmp(name, "INBOX") == 0) {
- /* inbox always exists */
- flags = 0;
- } else {
- flags = str_path(path, sizeof(path), storage->dir, name) == 0 &&
- stat(path, &st) == 0 && !S_ISDIR(st.st_mode) ?
- 0 : MAILBOX_NOSELECT;
+ if (ctx->dir->virtual_path != NULL)
+ ctx->next = mbox_list_path;
+ else
+ ctx->next = mbox_list_next;
+
+ /* INBOX exists always, even if the file doesn't. */
+ ctx->list.flags = MAILBOX_NOINFERIORS;
+ if ((ctx->flags & MAILBOX_LIST_NO_FLAGS) == 0) {
+ if (stat(ctx->storage->inbox_file, &st) < 0)
+ ctx->list.flags |= MAILBOX_UNMARKED;
+ else
+ ctx->list.flags |= STAT_GET_MARKED(st);
}
- ctx->callback(storage, name, flags, ctx->context);
- return TRUE;
+ ctx->list.name = "INBOX";
+ return &ctx->list;
+}
+
+static struct mailbox_list *mbox_list_path(struct mailbox_list_context *ctx)
+{
+ ctx->next = mbox_list_next;
+
+ ctx->list.flags = MAILBOX_NOSELECT;
+ ctx->list.name = p_strconcat(ctx->list_pool,
+ ctx->dir->virtual_path, "/", NULL);
+
+ if (imap_match(ctx->glob, ctx->list.name) > 0)
+ return &ctx->list;
+ else
+ return ctx->next(ctx);
}
-int mbox_find_subscribed(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context)
+static struct mailbox_list *mbox_list_next(struct mailbox_list_context *ctx)
{
- struct find_subscribed_context ctx;
+ struct list_dir_context *dir;
+ struct dirent *d;
+ int ret;
- ctx.callback = callback;
- ctx.context = context;
+ p_clear(ctx->list_pool);
- if (subsfile_foreach(storage, mask, mbox_subs_cb, &ctx) <= 0)
- return FALSE;
+ while (ctx->dir != NULL) {
+ /* NOTE: list_file() may change ctx->dir */
+ while ((d = readdir(ctx->dir->dirp)) != NULL) {
+ t_push();
+ ret = list_file(ctx, d->d_name);
+ t_pop();
+
+ if (ret > 0)
+ return &ctx->list;
+ if (ret < 0) {
+ ctx->failed = TRUE;
+ return NULL;
+ }
+ }
+
+ dir = ctx->dir;
+ ctx->dir = dir->prev;
+ list_dir_context_free(dir);
+ }
- return TRUE;
+ /* finished */
+ return NULL;
}
mbox_create_mailbox,
mbox_delete_mailbox,
mbox_rename_mailbox,
- mbox_find_mailboxes,
+ mbox_list_mailbox_init,
+ mbox_list_mailbox_deinit,
+ mbox_list_mailbox_next,
subsfile_set_subscribed,
- mbox_find_subscribed,
mbox_get_mailbox_name_status,
mail_storage_get_last_error,
time_t received_date, int timezone_offset,
struct istream *data);
-int mbox_find_mailboxes(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context);
-int mbox_find_subscribed(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t callback, void *context);
+struct mailbox_list_context *
+mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
+ enum mailbox_list_flags flags, int *sorted);
+int mbox_list_mailbox_deinit(struct mailbox_list_context *ctx);
+struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx);
int mbox_expunge_locked(struct index_mailbox *ibox, int notify);
struct client_workaround_list client_workaround_list[] = {
{ "oe6-fetch-no-newmail", WORKAROUND_OE6_FETCH_NO_NEWMAIL },
+ { "list-sort", WORKAROUND_LIST_SORT },
{ NULL, 0 }
};
list = client_workaround_list;
for (; list->name != NULL; list++) {
- if (strcasecmp(*str, list->name) == 0)
+ if (strcasecmp(*str, list->name) == 0) {
client_workarounds |= list->num;
- else
- i_fatal("Unknown client workaround: %s", *str);
+ break;
+ }
}
+ if (list->name == NULL)
+ i_fatal("Unknown client workaround: %s", *str);
}
}
#include "imap-util.h"
+enum mailbox_list_flags {
+ MAILBOX_LIST_SUBSCRIBED = 0x01, /* show only subscribed */
+ MAILBOX_LIST_NO_FLAGS = 0x02 /* don't set mailbox_flags */
+};
+
enum mailbox_flags {
MAILBOX_NOSELECT = 0x01,
MAILBOX_CHILDREN = 0x02,
};
enum client_workarounds {
- WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01
+ WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01,
+ WORKAROUND_LIST_SORT = 0x02
};
struct mail_full_flags {
struct mail_storage;
struct mail_storage_callbacks;
+struct mailbox_list;
struct mailbox_status;
struct mail_search_arg;
struct fetch_context;
struct search_context;
-typedef void mailbox_list_callback_t(struct mail_storage *storage,
- const char *name, enum mailbox_flags flags,
- void *context);
-
/* All methods returning int return either TRUE or FALSE. */
struct mail_storage {
char *name;
int (*rename_mailbox)(struct mail_storage *storage, const char *oldname,
const char *newname);
- /* Execute specified function for all mailboxes matching given
- mask. The mask is in RFC2060 LIST format. */
- int (*find_mailboxes)(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t *callback, void *context);
+ /* Initialize new mailbox list request. mask may contain '%' and '*'
+ wildcards as defined in RFC2060. Matching against "INBOX" is
+ case-insensitive, but anything else is not. *sorted is set to TRUE
+ if the output will contain parent mailboxes always before their
+ children. */
+ struct mailbox_list_context *
+ (*list_mailbox_init)(struct mail_storage *storage,
+ const char *mask,
+ enum mailbox_list_flags flags,
+ int *sorted);
+ /* Deinitialize mailbox list request. Returns FALSE if some error
+ occured while listing. */
+ int (*list_mailbox_deinit)(struct mailbox_list_context *ctx);
+ /* Get next mailbox. Returns the mailbox name */
+ struct mailbox_list *
+ (*list_mailbox_next)(struct mailbox_list_context *ctx);
/* Subscribe/unsubscribe mailbox. There should be no error when
subscribing to already subscribed mailbox. Subscribing to
int (*set_subscribed)(struct mail_storage *storage,
const char *name, int set);
- /* Exactly like find_mailboxes(), but list only subscribed mailboxes. */
- int (*find_subscribed)(struct mail_storage *storage, const char *mask,
- mailbox_list_callback_t *callback,
- void *context);
-
/* Returns mailbox name status */
int (*get_mailbox_name_status)(struct mail_storage *storage,
const char *name,
enum mail_fetch_field field);
};
+struct mailbox_list {
+ const char *name;
+ enum mailbox_flags flags;
+};
+
struct mailbox_status {
unsigned int messages;
unsigned int recent;
-/* Copyright (C) 2002 Timo Sirainen */
-
-/* ugly code here - text files are annoying to manage */
+/* Copyright (C) 2002-2003 Timo Sirainen */
#include "lib.h"
+#include "istream.h"
+#include "ostream.h"
#include "file-lock.h"
-#include "mmap-util.h"
#include "write-full.h"
-#include "imap-match.h"
#include "mail-storage.h"
#include "subscription-file.h"
#include <fcntl.h>
#define SUBSCRIPTION_FILE_NAME ".subscriptions"
+#define MAX_MAILBOX_LENGTH PATH_MAX
+
+struct subsfile_list_context {
+ pool_t pool;
+
+ struct mail_storage *storage;
+ struct istream *input;
+ const char *path;
+
+ int failed;
+};
static int subsfile_set_syscall_error(struct mail_storage *storage,
const char *path, const char *function)
}
static int subscription_open(struct mail_storage *storage, int update,
- const char **path, void **mmap_base,
- size_t *mmap_length)
+ const char **path)
{
int fd;
return -1;
}
- return -2;
+ return -1;
}
/* FIXME: we should work without locking, rename() would be easiest
(void)close(fd);
return -1;
}
+ return fd;
+}
- *mmap_base = update ? mmap_rw_file(fd, mmap_length) :
- mmap_ro_file(fd, mmap_length);
- if (*mmap_base == MAP_FAILED) {
- *mmap_base = NULL;
- subsfile_set_syscall_error(storage, "mmap()", *path);
- (void)close(fd);
- return -1;
+static const char *next_line(struct mail_storage *storage, const char *path,
+ struct istream *input, int *failed)
+{
+ const char *line;
+
+ while ((line = i_stream_next_line(input)) == NULL) {
+ switch (i_stream_read(input)) {
+ case -1:
+ *failed = FALSE;
+ return NULL;
+ case -2:
+ /* mailbox name too large */
+ mail_storage_set_critical(storage,
+ "Subscription file %s contains lines longer "
+ "than %u characters", path,
+ MAX_MAILBOX_LENGTH);
+ *failed = TRUE;
+ return NULL;
+ }
}
- (void)madvise(*mmap_base, *mmap_length, MADV_SEQUENTIAL);
- return fd;
+ *failed = FALSE;
+ return line;
}
-static int subscription_append(struct mail_storage *storage, int fd,
- const char *name, size_t len, int prefix_lf,
- const char *path)
+static int stream_cut(struct mail_storage *storage, const char *path,
+ struct istream *input, uoff_t count)
{
- char *buf;
-
- if (lseek(fd, 0, SEEK_END) < 0)
- return subsfile_set_syscall_error(storage, "lseek()", path);
-
- /* @UNSAFE */
- buf = t_buffer_get(len+2);
- buf[0] = '\n';
- memcpy(buf+1, name, len);
- buf[len+1] = '\n';
-
- if (prefix_lf)
- len += 2;
- else {
- buf++;
- len++;
+ struct ostream *output;
+ int fd, failed;
+
+ fd = i_stream_get_fd(input);
+ i_assert(fd != -1);
+
+ output = o_stream_create_file(fd, default_pool, 4096, 0, 0);
+ if (o_stream_seek(output, input->start_offset + input->v_offset) < 0) {
+ failed = TRUE;
+ errno = output->stream_errno;
+ subsfile_set_syscall_error(storage, "o_stream_seek()", path);
+ } else {
+ i_stream_skip(input, count);
+ failed = o_stream_send_istream(output, input) < 0;
+ if (failed) {
+ errno = output->stream_errno;
+ subsfile_set_syscall_error(storage,
+ "o_stream_send_istream()",
+ path);
+ }
}
- if (write_full(fd, buf, len) < 0) {
- subsfile_set_syscall_error(storage, "write_full()", path);
- return FALSE;
+ if (!failed) {
+ if (ftruncate(fd, output->offset) < 0) {
+ subsfile_set_syscall_error(storage, "ftruncate()",
+ path);
+ failed = TRUE;
+ }
}
- return TRUE;
+ o_stream_unref(output);
+ return !failed;
}
int subsfile_set_subscribed(struct mail_storage *storage,
const char *name, int set)
{
- void *mmap_base;
- size_t mmap_length;
- const char *path;
- char *subscriptions, *end, *p;
- size_t namelen, afterlen, removelen;
- int fd, failed, prefix_lf;
+ const char *path, *line;
+ struct istream *input;
+ uoff_t offset;
+ int fd, failed;
if (strcasecmp(name, "INBOX") == 0)
name = "INBOX";
- fd = subscription_open(storage, TRUE, &path, &mmap_base, &mmap_length);
+ fd = subscription_open(storage, TRUE, &path);
if (fd == -1)
return FALSE;
- namelen = strlen(name);
-
- subscriptions = mmap_base;
- if (subscriptions == NULL)
- p = NULL;
- else {
- end = subscriptions + mmap_length;
- for (p = subscriptions; p != end; p++) {
- if (*p == *name && p+namelen <= end &&
- strncmp(p, name, namelen) == 0) {
- /* make sure beginning and end matches too */
- if ((p == subscriptions || p[-1] == '\n') &&
- (p+namelen == end || p[namelen] == '\n'))
- break;
- }
+ input = i_stream_create_file(fd, default_pool,
+ MAX_MAILBOX_LENGTH, FALSE);
+ do {
+ offset = input->v_offset;
+ line = next_line(storage, path, input, &failed);
+ } while (line != NULL && strcmp(line, name) != 0);
+
+ if (!failed) {
+ if (set && line == NULL) {
+ /* add subscription. we're at EOF so just write it */
+ write_full(fd, t_strconcat(name, "\n", NULL),
+ strlen(name)+1);
+ } else if (!set && line != NULL) {
+ /* remove subcription. */
+ uoff_t size = input->v_offset - offset;
+ i_stream_seek(input, offset);
+ if (!stream_cut(storage, path, input, size))
+ failed = TRUE;
}
-
- if (p == end)
- p = NULL;
}
- failed = FALSE;
- if (p != NULL && !set) {
- /* remove it */
- afterlen = mmap_length - (size_t) (p - subscriptions);
- removelen = namelen < afterlen ? namelen+1 : namelen;
-
- if (removelen < afterlen)
- memmove(p, p+removelen, afterlen-removelen);
-
- if (ftruncate(fd, (off_t) (mmap_length - removelen)) == -1) {
- subsfile_set_syscall_error(storage, "ftruncate()",
- path);
- failed = TRUE;
- }
- } else if (p == NULL && set) {
- /* append it */
- prefix_lf = mmap_length > 0 &&
- subscriptions[mmap_length-1] != '\n';
- if (!subscription_append(storage, fd, name, namelen,
- prefix_lf, path))
- failed = TRUE;
- }
-
- if (mmap_base != NULL && munmap(mmap_base, mmap_length) < 0) {
- subsfile_set_syscall_error(storage, "munmap()", path);
- failed = TRUE;
- }
+ i_stream_unref(input);
if (close(fd) < 0) {
subsfile_set_syscall_error(storage, "close()", path);
return !failed;
}
-int subsfile_foreach(struct mail_storage *storage, const char *mask,
- subsfile_foreach_callback_t *callback, void *context)
+struct subsfile_list_context *
+subsfile_list_init(struct mail_storage *storage)
{
- struct imap_match_glob *glob;
- const char *path, *start, *end, *p, *line;
- void *mmap_base;
- size_t mmap_length;
- int fd, ret;
-
- fd = subscription_open(storage, FALSE, &path, &mmap_base, &mmap_length);
- if (fd < 0) {
- /* -2 = no subscription file, ignore */
- return fd == -1 ? -1 : 1;
- }
+ struct subsfile_list_context *ctx;
+ pool_t pool;
+ const char *path;
+ int fd;
- glob = imap_match_init(mask, TRUE, storage->hierarchy_sep);
+ fd = subscription_open(storage, FALSE, &path);
+ if (fd == -1 && errno != ENOENT)
+ return NULL;
- start = mmap_base; end = start + mmap_length; ret = 1;
- while (ret) {
- t_push();
+ pool = pool_alloconly_create("subsfile_list", MAX_MAILBOX_LENGTH+1024);
- for (p = start; p != end; p++) {
- if (*p == '\n')
- break;
- }
+ ctx = p_new(pool, struct subsfile_list_context, 1);
+ ctx->pool = pool;
+ ctx->storage = storage;
+ ctx->input = fd == -1 ? NULL :
+ i_stream_create_file(fd, pool, MAX_MAILBOX_LENGTH, TRUE);
+ ctx->path = p_strdup(pool, path);
+ return ctx;
+}
- line = t_strdup_until(start, p);
- if (line != NULL && *line != '\0' && imap_match(glob, line) > 0)
- ret = callback(storage, line, context);
- t_pop();
+int subsfile_list_deinit(struct subsfile_list_context *ctx)
+{
+ int failed;
- if (p == end)
- break;
- start = p+1;
- }
+ failed = ctx->failed;
+ if (ctx->input != NULL)
+ i_stream_unref(ctx->input);
+ pool_unref(ctx->pool);
- if (mmap_base != NULL && munmap(mmap_base, mmap_length) < 0)
- subsfile_set_syscall_error(storage, "munmap()", path);
- if (close(fd) < 0)
- subsfile_set_syscall_error(storage, "close()", path);
- return ret;
+ return !failed;
+}
+
+const char *subsfile_list_next(struct subsfile_list_context *ctx)
+{
+ if (ctx->failed || ctx->input == NULL)
+ return NULL;
+
+ return next_line(ctx->storage, ctx->path, ctx->input, &ctx->failed);
}
#include "mail-storage.h"
-/* Returns FALSE if foreach should be aborted */
-typedef int subsfile_foreach_callback_t(struct mail_storage *storage,
- const char *name, void *context);
+/* Initialize new subscription file listing. Returns NULL if failed. */
+struct subsfile_list_context *
+subsfile_list_init(struct mail_storage *storage);
+
+/* Deinitialize subscription file listing. Returns FALSE if some error occured
+ while listing. */
+int subsfile_list_deinit(struct subsfile_list_context *ctx);
+/* Returns the next subscribed mailbox, or NULL. */
+const char *subsfile_list_next(struct subsfile_list_context *ctx);
int subsfile_set_subscribed(struct mail_storage *storage,
const char *name, int set);
-/* Returns -1 if error, 0 if foreach function returned FALSE or 1 if all ok */
-int subsfile_foreach(struct mail_storage *storage, const char *mask,
- subsfile_foreach_callback_t *callback, void *context);
-
#endif