Mercurial > dovecot > original-hg > dovecot-1.2
changeset 1198:d28571e8c810 HEAD
Rewrote LIST, LSUB and subscription file handling. LIST replies aren't
sorted anymore by default, it can be enabled with client_workarounds =
list-sort.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 19 Feb 2003 21:55:27 +0200 |
parents | e86f259048cf |
children | 88c0d1c2b3c3 |
files | dovecot-example.conf src/imap/cmd-list.c src/lib-imap/imap-match.c src/lib-imap/imap-match.h src/lib-storage/index/maildir/maildir-list.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-storage.h src/lib-storage/index/mbox/mbox-list.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/mbox/mbox-storage.h src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib-storage/subscription-file/subscription-file.c src/lib-storage/subscription-file/subscription-file.h |
diffstat | 14 files changed, 861 insertions(+), 531 deletions(-) [+] |
line wrap: on
line diff
--- a/dovecot-example.conf Wed Feb 19 21:50:22 2003 +0200 +++ b/dovecot-example.conf Wed Feb 19 21:55:27 2003 +0200 @@ -215,6 +215,11 @@ # 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
--- a/src/imap/cmd-list.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/imap/cmd-list.c Wed Feb 19 21:55:27 2003 +0200 @@ -76,20 +76,6 @@ 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) { @@ -119,10 +105,77 @@ } } +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; @@ -137,50 +190,32 @@ } /* <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)
--- a/src/lib-imap/imap-match.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-imap/imap-match.c Wed Feb 19 21:55:27 2003 +0200 @@ -10,6 +10,8 @@ #include <ctype.h> struct imap_match_glob { + pool_t pool; + int inboxcase; const char *inboxcase_end; @@ -21,15 +23,16 @@ 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 */ @@ -79,13 +82,18 @@ } 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) { @@ -94,23 +102,24 @@ 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++; @@ -126,8 +135,8 @@ 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) @@ -139,18 +148,23 @@ 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; @@ -168,14 +182,14 @@ 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)) { @@ -187,5 +201,6 @@ } } - return *data == '\0' && *mask == '\0' ? 1 : 0; + return *data == '\0' && *mask == '\0' ? + IMAP_MATCH_YES : IMAP_MATCH_CHILDREN; }
--- a/src/lib-imap/imap-match.h Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-imap/imap-match.h Wed Feb 19 21:55:27 2003 +0200 @@ -1,15 +1,24 @@ #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); -/* 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); +void imap_match_deinit(struct imap_match_glob *glob); + +enum imap_match_result +imap_match(struct imap_match_glob *glob, const char *data); #endif
--- a/src/lib-storage/index/maildir/maildir-list.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-list.c Wed Feb 19 21:55:27 2003 +0200 @@ -12,11 +12,26 @@ #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) { @@ -72,50 +87,147 @@ 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)); + } + + ctx->dirp = opendir(ctx->dir); + if (ctx->dirp == NULL) { + mail_storage_set_critical(storage, "opendir(%s) failed: %m", + 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; } - dirp = opendir(dir); - if (dirp == NULL) { - mail_storage_set_critical(storage, "opendir(%s) failed: %m", - dir); - return FALSE; + 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(); } - glob = imap_match_init(mask, TRUE, '.'); + 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; +} - failed = found_inbox = FALSE; - while ((d = readdir(dirp)) != NULL) { +static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx) +{ + struct dirent *d; + struct stat st; + char path[PATH_MAX]; + int ret; + + if (ctx->dirp == NULL) + return NULL; + + while ((d = readdir(ctx->dirp)) != NULL) { const char *fname = d->d_name; if (fname[0] != '.') @@ -128,23 +240,24 @@ /* 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)) @@ -162,60 +275,43 @@ } 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) { + if (closedir(ctx->dirp) < 0) { + mail_storage_set_critical(ctx->storage, + "closedir(%s) failed: %m", ctx->dir); + ctx->failed = TRUE; + } + ctx->dirp = NULL; + + if (!ctx->found_inbox && imap_match(ctx->glob, "INBOX") > 0) { /* .INBOX directory doesn't exist yet, but INBOX still exists */ - callback(storage, "INBOX", 0, context); + ctx->list.flags = 0; + ctx->list.name = "INBOX"; + return &ctx->list; } - (void)closedir(dirp); - return !failed; + /* we're finished */ + return NULL; } -static int maildir_subs_cb(struct mail_storage *storage, const char *name, - void *context) +struct mailbox_list * +maildir_list_mailbox_next(struct mailbox_list_context *ctx) { - 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; - } - - ctx->callback(storage, name, flags, ctx->context); - return TRUE; + return ctx->next(ctx); } - -int maildir_find_subscribed(struct mail_storage *storage, const char *mask, - mailbox_list_callback_t callback, void *context) -{ - 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; -}
--- a/src/lib-storage/index/maildir/maildir-storage.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.c Wed Feb 19 21:55:27 2003 +0200 @@ -120,8 +120,7 @@ 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); @@ -431,43 +430,63 @@ 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; + struct mailbox_list_context *ctx; + struct mailbox_list *list; + const char *oldpath, *newpath, *new_listname; + size_t oldnamelen; + int sorted, ret; - i_assert(ctx->oldnamelen <= strlen(name)); + ret = 0; + oldnamelen = strlen(oldname); - newname = t_strconcat(ctx->newname, ".", name + ctx->oldnamelen, NULL); + 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)); - oldpath = maildir_get_path(storage, name); - newpath = maildir_get_path(storage, newname); + 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. + /* 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) - ctx->found = TRUE; - else { - mail_storage_set_critical(storage, "rename(%s, %s) failed: %m", - oldpath, newpath); + 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; + } + + (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); @@ -495,19 +514,16 @@ 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; } @@ -581,9 +597,10 @@ 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,
--- a/src/lib-storage/index/maildir/maildir-storage.h Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.h Wed Feb 19 21:55:27 2003 +0200 @@ -14,14 +14,19 @@ 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
--- a/src/lib-storage/index/mbox/mbox-list.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-list.c Wed Feb 19 21:55:27 2003 +0200 @@ -11,154 +11,37 @@ #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_context { - struct mail_storage *storage; - struct imap_match_glob *glob; - mailbox_list_callback_t *callback; - void *context; +struct list_dir_context { + struct list_dir_context *prev; - const char *rootdir; + DIR *dirp; + char *real_path, *virtual_path; }; -static int mbox_find_path(struct list_context *ctx, const char *relative_dir) -{ - 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; - - t_push(); - - 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; - } - - dir = fulldir; - } - - dir = home_expand(dir); - dirp = opendir(dir); - if (dirp == NULL) { - t_pop(); +struct mailbox_list_context { + struct mail_storage *storage; + enum mailbox_list_flags flags; - if (relative_dir != NULL && - (errno == ENOENT || errno == ENOTDIR)) { - /* probably just race condition with other client - deleting the mailbox. */ - return TRUE; - } - - if (errno == EACCES) { - if (relative_dir != NULL) { - /* subfolder, ignore */ - return TRUE; - } - mail_storage_set_error(ctx->storage, "Access denied"); - return FALSE; - } + struct imap_match_glob *glob; + struct subsfile_list_context *subsfile_ctx; - mail_storage_set_critical(ctx->storage, - "opendir(%s) failed: %m", dir); - return FALSE; - } - - failed = FALSE; - while ((d = readdir(dirp)) != NULL) { - const char *fname = d->d_name; - - /* skip all hidden files */ - if (fname[0] == '.') - continue; - - /* skip all .lock files */ - len = strlen(fname); - if (len > 5 && strcmp(fname+len-5, ".lock") == 0) - continue; + int failed; - /* 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 ((match = imap_match(ctx->glob, listpath)) < 0) - continue; - - /* 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; - } - - if (stat(fullpath, &st) < 0) { - if (errno == ENOENT) - continue; /* just deleted, ignore */ + struct mailbox_list *(*next)(struct mailbox_list_context *ctx); - mail_storage_set_critical(ctx->storage, - "stat(%s) failed: %m", - fullpath); - 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(); + pool_t list_pool; + struct mailbox_list list; + struct list_dir_context *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(); - } - } - - t_pop(); - - (void)closedir(dirp); - return !failed; -} +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) { @@ -173,93 +56,335 @@ return last_dir == NULL ? NULL : t_strdup_until(mask, last_dir); } -int mbox_find_mailboxes(struct mail_storage *storage, const char *mask, - mailbox_list_callback_t callback, void *context) +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); +} + +static int list_opendir(struct mail_storage *storage, + const char *path, int root, DIR **dirp) { - struct list_context ctx; - struct imap_match_glob *glob; - const char *relative_dir; + *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 (errno == EACCES) { + if (!root) { + /* subfolder, ignore */ + return 0; + } + mail_storage_set_error(storage, "Access denied"); + return -1; + } + + mail_storage_set_critical(storage, "opendir(%s) failed: %m", path); + return -1; +} + +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; + + *sorted = (flags & MAILBOX_LIST_SUBSCRIBED) == 0; /* 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; + return NULL; } mail_storage_clear_error(storage); + 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 we're matching only subdirectories, don't bother scanning the parent directories */ - relative_dir = mask_get_dir(mask); + 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; - 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); + 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; +} + +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; + + if (ctx->subsfile_ctx != NULL) { + if (!subsfile_list_deinit(ctx->subsfile_ctx)) + failed = TRUE; } - memset(&ctx, 0, sizeof(ctx)); - ctx.storage = storage; - ctx.glob = glob; - ctx.callback = callback; - ctx.context = context; + while (ctx->dir != NULL) { + struct list_dir_context *dir = ctx->dir; + + ctx->dir = dir->prev; + list_dir_context_free(dir); + } + + if (ctx->list_pool != NULL) + pool_unref(ctx->list_pool); + imap_match_deinit(ctx->glob); + i_free(ctx); + + return !failed; +} + +struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx) +{ + return ctx->next(ctx); +} - if (!full_filesystem_access || relative_dir == NULL || - (*relative_dir != '/' && *relative_dir != '~' && - *relative_dir != '\0')) - ctx.rootdir = storage->dir; - else - ctx.rootdir = ""; +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; - if (relative_dir != NULL) { - const char *matchdir = t_strconcat(relative_dir, "/", NULL); + /* check the mask */ + if (ctx->dir->virtual_path == NULL) + list_path = fname; + else { + list_path = t_strconcat(ctx->dir->virtual_path, + "/", fname, NULL); + } - if (imap_match(ctx.glob, matchdir) > 0) { - t_push(); - ctx.callback(ctx.storage, matchdir, MAILBOX_NOSELECT, - ctx.context); - t_pop(); - } + 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; } - if (!mbox_find_path(&ctx, relative_dir)) - return FALSE; + 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); + } - return TRUE; + 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; + } + + return 0; } -static int mbox_subs_cb(struct mail_storage *storage, const char *name, - void *context) +static struct mailbox_list *mbox_list_subs(struct mailbox_list_context *ctx) { - struct find_subscribed_context *ctx = context; - enum mailbox_flags flags; struct stat st; - char path[PATH_MAX]; + 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; - /* 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; + 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(); } - ctx->callback(storage, name, flags, ctx->context); - 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 struct mailbox_list *mbox_list_inbox(struct mailbox_list_context *ctx) +{ + struct stat st; + + 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->list.name = "INBOX"; + return &ctx->list; } -int mbox_find_subscribed(struct mail_storage *storage, const char *mask, - mailbox_list_callback_t callback, void *context) +static struct mailbox_list *mbox_list_path(struct mailbox_list_context *ctx) { - struct find_subscribed_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); +} + +static struct mailbox_list *mbox_list_next(struct mailbox_list_context *ctx) +{ + struct list_dir_context *dir; + struct dirent *d; + int ret; + + p_clear(ctx->list_pool); - ctx.callback = callback; - ctx.context = context; + 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 (subsfile_foreach(storage, mask, mbox_subs_cb, &ctx) <= 0) - return FALSE; + if (ret > 0) + return &ctx->list; + if (ret < 0) { + ctx->failed = TRUE; + return NULL; + } + } - return TRUE; + dir = ctx->dir; + ctx->dir = dir->prev; + list_dir_context_free(dir); + } + + /* finished */ + return NULL; }
--- a/src/lib-storage/index/mbox/mbox-storage.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.c Wed Feb 19 21:55:27 2003 +0200 @@ -645,9 +645,10 @@ 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,
--- a/src/lib-storage/index/mbox/mbox-storage.h Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.h Wed Feb 19 21:55:27 2003 +0200 @@ -14,10 +14,11 @@ 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);
--- a/src/lib-storage/mail-storage.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/mail-storage.c Wed Feb 19 21:55:27 2003 +0200 @@ -23,6 +23,7 @@ struct client_workaround_list client_workaround_list[] = { { "oe6-fetch-no-newmail", WORKAROUND_OE6_FETCH_NO_NEWMAIL }, + { "list-sort", WORKAROUND_LIST_SORT }, { NULL, 0 } }; @@ -48,11 +49,13 @@ 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); } }
--- a/src/lib-storage/mail-storage.h Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/mail-storage.h Wed Feb 19 21:55:27 2003 +0200 @@ -5,6 +5,11 @@ #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, @@ -86,7 +91,8 @@ }; enum client_workarounds { - WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01 + WORKAROUND_OE6_FETCH_NO_NEWMAIL = 0x01, + WORKAROUND_LIST_SORT = 0x02 }; struct mail_full_flags { @@ -98,15 +104,12 @@ 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; @@ -156,10 +159,22 @@ 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 @@ -167,11 +182,6 @@ 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, @@ -356,6 +366,11 @@ enum mail_fetch_field field); }; +struct mailbox_list { + const char *name; + enum mailbox_flags flags; +}; + struct mailbox_status { unsigned int messages; unsigned int recent;
--- a/src/lib-storage/subscription-file/subscription-file.c Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/subscription-file/subscription-file.c Wed Feb 19 21:55:27 2003 +0200 @@ -1,12 +1,10 @@ -/* 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" @@ -14,6 +12,17 @@ #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) @@ -27,8 +36,7 @@ } 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; @@ -42,7 +50,7 @@ return -1; } - return -2; + return -1; } /* FIXME: we should work without locking, rename() would be easiest @@ -52,115 +60,108 @@ (void)close(fd); return -1; } - - *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; - } - - (void)madvise(*mmap_base, *mmap_length, MADV_SEQUENTIAL); return fd; } -static int subscription_append(struct mail_storage *storage, int fd, - const char *name, size_t len, int prefix_lf, - const char *path) +static const char *next_line(struct mail_storage *storage, const char *path, + struct istream *input, int *failed) { - char *buf; - - if (lseek(fd, 0, SEEK_END) < 0) - return subsfile_set_syscall_error(storage, "lseek()", path); + const char *line; - /* @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++; + 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; + } } - if (write_full(fd, buf, len) < 0) { - subsfile_set_syscall_error(storage, "write_full()", path); - return FALSE; + *failed = FALSE; + return line; +} + +static int stream_cut(struct mail_storage *storage, const char *path, + struct istream *input, uoff_t count) +{ + 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); + } } - return TRUE; + if (!failed) { + if (ftruncate(fd, output->offset) < 0) { + subsfile_set_syscall_error(storage, "ftruncate()", + path); + failed = 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); + 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); - 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; - } + 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); @@ -169,45 +170,45 @@ 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; + struct subsfile_list_context *ctx; + pool_t pool; + const char *path; + int fd; - fd = subscription_open(storage, FALSE, &path, &mmap_base, &mmap_length); - if (fd < 0) { - /* -2 = no subscription file, ignore */ - return fd == -1 ? -1 : 1; - } + fd = subscription_open(storage, FALSE, &path); + if (fd == -1 && errno != ENOENT) + return NULL; - glob = imap_match_init(mask, TRUE, storage->hierarchy_sep); - - 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); + + return !failed; +} - 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; +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); }
--- a/src/lib-storage/subscription-file/subscription-file.h Wed Feb 19 21:50:22 2003 +0200 +++ b/src/lib-storage/subscription-file/subscription-file.h Wed Feb 19 21:55:27 2003 +0200 @@ -3,15 +3,17 @@ #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