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